{
  "schema_version": "1.0.0",
  "metadata": {
    "name": "Smart flow"
  },
  "pipeline": {
    "items": [
      {
        "content_type": "trigger",
        "x": 564,
        "y": 372,
        "content_object": {
          "config": {},
          "event": 17
        }
      },
      {
        "content_type": "task",
        "x": 972,
        "y": 372,
        "content_object": {
          "type": "custom",
          "custom": "$custom_1",
          "secret": null,
          "kwargs": {
            "event_kwargs": {}
          },
          "team_api_key_public_id": "$team_api_key_1"
        }
      },
      {
        "content_type": "task",
        "x": 972,
        "y": 108,
        "content_object": {
          "type": "custom",
          "custom": "$custom_2",
          "secret": null,
          "kwargs": {
            "event_kwargs": {}
          },
          "team_api_key_public_id": "$team_api_key_1"
        }
      },
      {
        "content_type": "trigger",
        "x": 564,
        "y": 12,
        "content_object": {
          "scopes": [
            {
              "type": "team_scope"
            }
          ],
          "config": {
            "src_type": "any",
            "src_value": "any",
            "target_value": [
              "$role_1"
            ],
            "target_type": "any_of"
          },
          "event": 67
        }
      },
      {
        "content_type": "trigger",
        "x": 564,
        "y": 180,
        "content_object": {
          "scopes": [
            {
              "type": "team_scope"
            }
          ],
          "config": {
            "src_type": "any",
            "src_value": "any",
            "target_value": "$role_1",
            "target_type": "match"
          },
          "event": 69
        }
      }
    ],
    "item_connections": [
      {
        "parent_idx": 0,
        "child_idx": 1,
        "start": "right",
        "end": "left",
        "connection_type": "success"
      },
      {
        "parent_idx": 3,
        "child_idx": 2,
        "start": "right",
        "end": "left",
        "connection_type": "success"
      },
      {
        "parent_idx": 4,
        "child_idx": 2,
        "start": "right",
        "end": "left",
        "connection_type": "success"
      }
    ]
  },
  "resources": {
    "$custom_1": {
      "type": "custom_action",
      "label": "",
      "description": null,
      "source_nodes": [
        1
      ],
      "source_action_name": "smart_flow_1__request_handler",
      "source_action_data": {
        "name": "smart_flow_1__request_handler",
        "description": "",
        "file": "%22%22%22smart_flow_1__request_handler%20%E2%80%94%20contributor%20task%20request%20webhook.%0A%0AStandalone%20Orchestrate%20action%3A%20this%20single%20file%20deploys%20as-is%2C%20no%20build%20step.%0ACommon%20helpers%20%28errors%2C%20envelope%2C%20explore%20search%29%20are%20inlined%20below%3B%0Acustom_actions/Shared.py%20is%20the%20reference%20they%20were%20taken%20from.%0A%0AHandles%20the%20%60incoming_webhook%60%20request%20sent%20by%20the%20user-profile%20web%0Acomponent%20when%20a%20contributor%20clicks%20%22Request%22%20on%20their%20profile%20item.%0AFlow%3A%20validate%20payload%20-%3E%20read%20the%20item%20-%3E%20verify%20it%20is%20a%20user-profile%20item%0A-%3E%20verify%20it%20is%20assigned%20to%20the%20requesting%20user%20-%3E%20read%20the%20component%20config%0A-%3E%20verify%20the%20requested%20tasks%20are%20granted%20on%20this%20profile%20item%20%28plain%0Amembership%20in%20its%20allowedTaskIds%3B%20the%20config%27s%20allowedByDefault%20flag%20is%20only%20a%0Acreation-time%20seed%20applied%20by%20the%20profile%20handler%20and%20grants%20nothing%20here%29%0A-%3E%20derive%20the%20number%20of%20items%20to%20assign%20-%3E%20query%20eligible%0Aitems%20-%3E%20assign%20them%20under%20a%20per-project%2Btask%20lock%20-%3E%20return%20the%20result%20envelope.%20Authentication%20is%20handled%20by%20the%0ASuperAnnotate%20platform%20before%20the%20webhook%20reaches%20this%20handler.%0A%22%22%22%0A%0Aimport%20functools%0Aimport%20json%0Aimport%20math%0Aimport%20os%0Aimport%20sys%0Aimport%20time%0Aimport%20traceback%0Aimport%20urllib.error%0Aimport%20urllib.request%0Aimport%20uuid%0A%0Aimport%20boto3%0A%0A%23%20TypedDict%20compatibility%20for%20Python%20%3C%203.12.%20typing_extensions%20is%20installed%0A%23%20locally%20%28pydantic-validated%20SDK%20calls%20need%20the%20patch%29%20but%20missing%20in%20the%0A%23%20Orchestrate%20runtime%2C%20so%20the%20patch%20is%20best-effort%20%E2%80%94%20a%20hard%20import%20would%0A%23%20break%20the%20deployed%20action.%0Aif%20sys.version_info%20%3C%20%283%2C%2012%29%3A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20import%20typing%0A%20%20%20%20%20%20%20%20import%20typing_extensions%0A%0A%20%20%20%20%20%20%20%20typing.TypedDict%20%3D%20typing_extensions.TypedDict%0A%20%20%20%20%20%20%20%20typing.NotRequired%20%3D%20typing_extensions.NotRequired%0A%20%20%20%20%20%20%20%20typing.Required%20%3D%20typing_extensions.Required%0A%20%20%20%20except%20ModuleNotFoundError%3A%0A%20%20%20%20%20%20%20%20pass%0A%0Afrom%20superannotate%20import%20SAClient%0A%0A%23%20.env%20loading%20is%20local-development%20only%3B%20the%20Orchestrate%20runtime%20injects%20the%0A%23%20selected%20managed%20Team%20API%20key%20as%20SA_TOKEN.%0Atry%3A%0A%20%20%20%20from%20dotenv%20import%20load_dotenv%0A%0A%20%20%20%20load_dotenv%28%29%0Aexcept%20ModuleNotFoundError%3A%0A%20%20%20%20pass%0A%0AACTION_NAME%20%3D%20%22smart_flow_1__request_handler%22%0ASA_TOKEN%20%3D%20os.environ.get%28%22SA_TOKEN%22%29%0AAWS_ACCESS_KEY_ID%20%3D%20%22AKIAVYUOYPLFLX335R7G%22%0AAWS_SECRET_ACCESS_KEY%20%3D%20%222HvIeqSbMSzBpvTVwylLudVSeTy9vnkFGNC6TAkM%22%0AAWS_DEFAULT_REGION%20%3D%20%22us-east-1%22%0AEXPLORE_SERVICE_URL%20%3D%20%22https%3A//explore-service.superannotate.com/api/v1%22%0AEXPLORE_HTTP_TIMEOUT_SEC%20%3D%2030%0A%0A%23%20The%20web%20component%27s%20id%20on%20the%20platform%3B%20its%20per-item%20value%20carries%20the%0A%23%20user-profile%20flag%20%28%7B%22userProfile%22%3A%20%22true%22%7C%22false%22%2C%20...%7D%29.%0AUSER_PROFILE_COMPONENT_ID%20%3D%20%22user_profile%22%0A%23%20The%20component%27s%20config%20storage%20key%20%28matches%20CONFIG_DATA_KEY%20in%20the%0A%23%20component%27s%20ConfigData.ts%29.%0ACONFIG_DATA_KEY%20%3D%20%22user-profile-config-data%22%0AITEMS_PER_REQUEST_MIN%20%3D%201%0AITEMS_PER_REQUEST_MAX%20%3D%2020%0AMAXIMUM_REQUESTED_ITEMS_MAX%20%3D%20100_000%0A%23%20assign_items%20is%20LAST-wins%20and%20SILENT%3A%20assigning%20an%20already-assigned%20item%0A%23%20REPLACES%20the%20previous%20contributor%20%28verified%20live%202026-07-23%29.%20The%0A%23%20per-project%2Btask%20DynamoDB%20lock%20serializes%20requesters%2C%20so%20the%20remaining%0A%23%20stale-read%20hazard%20is%20the%20explore%20index%20lag%20%28~1.2s%20measured%29%3A%20wait%20it%20out%0A%23%20after%20assigning%2C%20then%20verify%20each%20item%20still%20belongs%20to%20the%20requester.%0AASSIGN_PROPAGATION_SLEEP_SEC%20%3D%201.5%0A%23%20Query%20more%20candidates%20than%20needed%2C%20then%20pick%20the%20lowest%20item%20names%20first.%0ACANDIDATE_POOL_SIZE%20%3D%2050%0A%23%20Pause%20before%20re-querying%20when%20a%20round%20produced%20nothing%20usable.%0ARETRY_SLEEP_SEC%20%3D%201.0%0AMAX_ASSIGN_ROUNDS%20%3D%2010%0A%0A%23%20Per-project%20mutex%20via%20DynamoDB%20conditional%20writes%3A%20only%20one%20Request%20handler%0A%23%20per%20project%20runs%20its%20assignment%20section%20at%20a%20time.%20The%20runtime%27s%20AWS_%2A%0A%23%20secrets%20belong%20to%20an%20IAM%20user%20allowed%20only%20PutItem/DeleteItem%20on%20this%20table.%0ALOCKS_TABLE_NAME%20%3D%20os.environ.get%28%22LOCKS_TABLE_NAME%22%2C%20%22sa-smart-flow-locks%22%29%0ALOCK_LEASE_SEC%20%3D%20120%20%20%23%20crash%20recovery%3A%20a%20stuck%20lock%20auto-expires%20after%20this%0A%23%20Max%20time%20a%20request%20waits%20for%20its%20turn.%20Each%20holder%20keeps%20the%20lock%20~5-6s%0A%23%20%28measured%20with%2020%20concurrent%20requesters%29%2C%20so%20300s%20covers%20a%20queue%20of%20~50.%0ALOCK_ACQUIRE_TIMEOUT_SEC%20%3D%20300%0ALOCK_POLL_SEC%20%3D%200.5%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Errors%2C%20logging%2C%20result%20envelope%0A%23%20---------------------------------------------------------------------------%0A%0Aclass%20ActionError%28Exception%29%3A%0A%20%20%20%20%22%22%22Base%20class%20for%20expected%2C%20handled%20action%20failures.%22%22%22%0A%0A%0Aclass%20ValidationError%28ActionError%29%3A%0A%20%20%20%20%22%22%22A%20required%20value%20was%20missing%20or%20invalid%20in%20the%20event/context.%22%22%22%0A%0A%0Aclass%20RequestStateError%28ActionError%29%3A%0A%20%20%20%20%22%22%22A%20valid%20request%20is%20disallowed%20by%20current%20server-owned%20request%20state.%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20error_code%2C%20message%29%3A%0A%20%20%20%20%20%20%20%20super%28%29.__init__%28message%29%0A%20%20%20%20%20%20%20%20self.error_code%20%3D%20error_code%0A%0A%0Aclass%20LockContentionError%28ActionError%29%3A%0A%20%20%20%20%22%22%22The%20request%20lock%20remained%20held%20until%20the%20acquisition%20timeout.%22%22%22%0A%0A%20%20%20%20error_code%20%3D%20%22request_busy%22%0A%0A%0Aclass%20ConfigurationError%28ActionError%29%3A%0A%20%20%20%20%22%22%22Published%20assignment-affecting%20component%20configuration%20is%20invalid.%22%22%22%0A%0A%0Aclass%20ExternalServiceError%28ActionError%29%3A%0A%20%20%20%20%22%22%22An%20SDK%20call%20or%20external%20HTTP%20request%20failed.%22%22%22%0A%0A%0Aclass%20LockInfrastructureError%28ActionError%29%3A%0A%20%20%20%20%22%22%22DynamoDB%20cannot%20safely%20provide%20the%20required%20assignment%20lock.%22%22%22%0A%0A%0Adef%20log%28message%29%3A%0A%20%20%20%20print%28%22%3E%3E%3E%20INFO%20%3E%3E%3E%20%22%2C%20message%2C%20%22%5Cn%22%29%0A%0A%0Aclass%20Run%3A%0A%20%20%20%20%22%22%22Records%20handler%20operations%20and%20builds%20the%20standard%20response%20envelope.%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20action_name%29%3A%0A%20%20%20%20%20%20%20%20self.action%20%3D%20action_name%0A%20%20%20%20%20%20%20%20self.operations%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.output%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20record%28self%2C%20op%2C%20ok%3DTrue%2C%20error%3D%22%22%2C%20%2A%2Acontext%29%3A%0A%20%20%20%20%20%20%20%20entry%20%3D%20%7B%22op%22%3A%20op%2C%20%22ok%22%3A%20ok%2C%20%22error%22%3A%20error%2C%20%2A%2Acontext%7D%0A%20%20%20%20%20%20%20%20self.operations.append%28entry%29%0A%20%20%20%20%20%20%20%20return%20entry%0A%0A%20%20%20%20def%20envelope%28self%2C%20ok%2C%20error%3DNone%2C%20error_type%3DNone%2C%20error_code%3DNone%29%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ok%22%3A%20ok%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22action%22%3A%20self.action%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22error%22%3A%20error%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22operations%22%3A%20self.operations%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20if%20error_type%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%5B%22error_type%22%5D%20%3D%20error_type%0A%20%20%20%20%20%20%20%20if%20error_code%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%5B%22error_code%22%5D%20%3D%20error_code%0A%20%20%20%20%20%20%20%20if%20self.output%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%5B%22output%22%5D%20%3D%20self.output%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%0Adef%20action%28func%29%3A%0A%20%20%20%20%22%22%22Wrap%20normal%20failures%20in%20the%20uniform%20result%20envelope.%0A%0A%20%20%20%20Only%20validation%2C%20server-state%2C%20and%20lock-contention%20outcomes%20retain%20the%0A%20%20%20%20request%20webhook%27s%20response-envelope%20contract.%20Configuration%2C%20lock%0A%20%20%20%20infrastructure%2C%20external-service%2C%20and%20unexpected%20failures%20are%20re-raised%0A%20%20%20%20after%20logging%20so%20Orchestrate%20marks%20the%20action%20execution%20as%20failed.%0A%0A%20%20%20%20Expected%20failures%20%28ActionError%20subclasses%29%20are%20logged%20without%20a%20traceback%3B%0A%20%20%20%20anything%20else%20is%20a%20bug%20and%20keeps%20the%20full%20traceback.%20The%20wrapped%20handler%20signature%20is%0A%20%20%20%20%60handler%28event%2C%20context%2C%20run%29%60%3B%20the%20SA%20runtime%20still%20calls%20it%20as%0A%20%20%20%20%60handler%28event%2C%20context%29%60.%22%22%22%0A%0A%20%20%20%20%40functools.wraps%28func%29%0A%20%20%20%20def%20wrapper%28event%2C%20context%29%3A%0A%20%20%20%20%20%20%20%20run%20%3D%20Run%28ACTION_NAME%29%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20func%28event%2C%20context%2C%20run%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20run.envelope%28ok%3DTrue%29%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20expected%20%3D%20isinstance%28error%2C%20ActionError%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28%22%3D%22%20%2A%2060%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20ACTION%20FAILED%20%28%7B%27handled%27%20if%20expected%20else%20%27UNEXPECTED%27%7D%29%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20%20%20action%20%3A%20%7Brun.action%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20%20%20type%20%20%20%3A%20%7Btype%28error%29.__name__%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20%20%20error%20%20%3A%20%7Berror%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20expected%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20traceback.print_exc%28%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28%22%3D%22%20%2A%2060%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28error%2C%20ValidationError%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20run.envelope%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ok%3DFalse%2C%20error%3Dstr%28error%29%2C%20error_type%3Dtype%28error%29.__name__%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28error%2C%20%28RequestStateError%2C%20LockContentionError%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20run.envelope%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ok%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20error%3Dstr%28error%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20error_type%3Dtype%28error%29.__name__%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20error_code%3Derror.error_code%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%0A%0A%20%20%20%20return%20wrapper%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20SuperAnnotate%20access%0A%23%20---------------------------------------------------------------------------%0A%0Adef%20get_sa_client%28%29%3A%0A%20%20%20%20if%20not%20isinstance%28SA_TOKEN%2C%20str%29%20or%20not%20SA_TOKEN.strip%28%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%22SA_TOKEN%20is%20missing%20or%20blank%22%29%0A%20%20%20%20return%20SAClient%28SA_TOKEN%29%0A%0A%0Adef%20get_team_id%28client%29%3A%0A%20%20%20%20%22%22%22Read%20the%20team%20id%20once%20from%20the%20managed%20token%27s%20SDK-authenticated%20team.%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20team_metadata%20%3D%20client.get_team_metadata%28%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28f%22could%20not%20read%20team%20metadata%3A%20%7Berror%7D%22%29%20from%20error%0A%20%20%20%20team_id%20%3D%20team_metadata.get%28%22id%22%29%20if%20isinstance%28team_metadata%2C%20dict%29%20else%20None%0A%20%20%20%20if%20isinstance%28team_id%2C%20bool%29%20or%20not%20isinstance%28team_id%2C%20int%29%20or%20team_id%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22team%20metadata%20had%20no%20positive%20integer%20id%3A%20%7Bteam_metadata%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20return%20team_id%0A%0A%0Adef%20query_items%28project_id%2C%20query%2C%20limit%2C%20team_id%29%3A%0A%20%20%20%20%22%22%22SAQuL%20item%20search%20straight%20against%20the%20explore%20service%2C%20with%20a%20hard%0A%20%20%20%20limit.%20Unlike%20SAClient.query%28%29%20%E2%80%94%20which%20pages%20through%20every%20matching%20item%0A%20%20%20%20with%20no%20way%20to%20stop%20early%20%28~7%20minutes%20on%2010k%20items%29%20%E2%80%94%20this%20asks%20the%0A%20%20%20%20backend%20for%20at%20most%20%60limit%60%20items%20in%20a%20single%20request.%20Returns%20a%20list%20of%0A%20%20%20%20item%20dicts%20%28includes%20name%20and%20folder_id%29.%22%22%22%0A%20%20%20%20url%20%3D%20%28%0A%20%20%20%20%20%20%20%20f%22%7BEXPLORE_SERVICE_URL%7D/items/search%22%0A%20%20%20%20%20%20%20%20f%22%3Fproject_id%3D%7Bproject_id%7D%26team_id%3D%7Bteam_id%7D%22%0A%20%20%20%20%20%20%20%20%22%26includeFolderNames%3Dtrue%26includeInstances%3Dfalse%26parseQuery%3Dtrue%22%0A%20%20%20%20%29%0A%20%20%20%20body%20%3D%20json.dumps%28%7B%22image_index%22%3A%200%2C%20%22limit%22%3A%20limit%2C%20%22query%22%3A%20query%7D%29%0A%20%20%20%20request%20%3D%20urllib.request.Request%28%0A%20%20%20%20%20%20%20%20url%2C%0A%20%20%20%20%20%20%20%20data%3Dbody.encode%28%22utf-8%22%29%2C%0A%20%20%20%20%20%20%20%20method%3D%22POST%22%2C%0A%20%20%20%20%20%20%20%20headers%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Authorization%22%3A%20SA_TOKEN%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22authType%22%3A%20%22api_key%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22application/json%22%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%29%0A%20%20%20%20log%28f%22POST%20%7Burl%7D%22%29%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20with%20urllib.request.urlopen%28%0A%20%20%20%20%20%20%20%20%20%20%20%20request%2C%20timeout%3DEXPLORE_HTTP_TIMEOUT_SEC%0A%20%20%20%20%20%20%20%20%29%20as%20response%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raw_body%20%3D%20response.read%28%29.decode%28%22utf-8%22%29%0A%20%20%20%20except%20urllib.error.HTTPError%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22explore%20search%20failed%20with%20status%20%7Berror.code%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Berror.read%28%29.decode%28%27utf-8%27%29%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20except%20TimeoutError%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22explore%20search%20timed%20out%20after%20%7BEXPLORE_HTTP_TIMEOUT_SEC%7Ds%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20except%20urllib.error.URLError%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28f%22explore%20search%20failed%3A%20%7Berror.reason%7D%22%29%20from%20error%0A%0A%20%20%20%20items%20%3D%20json.loads%28raw_body%29%20if%20raw_body%20else%20None%0A%20%20%20%20if%20not%20isinstance%28items%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22unexpected%20explore%20search%20response%20for%20project%20%7Bproject_id%7D%3A%20%7Bitems%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20return%20items%0A%0A%0Adef%20resolve_config_status_names%28client%2C%20project_id%2C%20config_tasks%2C%20run%29%3A%0A%20%20%20%20%22%22%22Resolve%20every%20configured%20portable%20status%20ID%20to%20its%20current%20status%20name.%0A%0A%20%20%20%20Explore%20filters%20items%20by%20status%20name%2C%20while%20task%20configuration%20persists%0A%20%20%20%20portable%20status%20IDs.%20The%20request%20payload%20is%20deliberately%20not%20authoritative%3A%0A%20%20%20%20a%20browser%20may%20be%20stale%2C%20replayed%2C%20or%20forged.%20Read%20the%20project%27s%20workflow%0A%20%20%20%20rows%20through%20the%20installed%20SDK%20instead.%0A%20%20%20%20%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20project%20%3D%20client.get_project_by_id%28project_id%29%0A%20%20%20%20%20%20%20%20workflow_id%20%3D%20project.get%28%22workflow_id%22%29%20if%20isinstance%28project%2C%20dict%29%20else%20None%0A%20%20%20%20%20%20%20%20if%20not%20is_finite_number%28workflow_id%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22project%20%7Bproject_id%7D%20has%20no%20finite%20workflow_id%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20workflow_response%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20client.controller.service_provider.work_management.list_workflow_statuses%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20project_id%2C%20int%28workflow_id%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20except%20ExternalServiceError%3A%0A%20%20%20%20%20%20%20%20raise%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20workflow%20statuses%20for%20project%20%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%0A%20%20%20%20if%20not%20getattr%28workflow_response%2C%20%22ok%22%2C%20False%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20workflow%20statuses%20for%20project%20%7Bproject_id%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bgetattr%28workflow_response%2C%20%27error%27%2C%20None%29%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20response_data%20%3D%20getattr%28workflow_response%2C%20%22data%22%2C%20None%29%0A%20%20%20%20workflow_statuses%20%3D%20%28%0A%20%20%20%20%20%20%20%20response_data.get%28%22data%22%29%20if%20isinstance%28response_data%2C%20dict%29%20else%20None%0A%20%20%20%20%29%0A%20%20%20%20if%20not%20isinstance%28workflow_statuses%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22workflow%20statuses%20for%20project%20%7Bproject_id%7D%20had%20an%20invalid%20response%20shape%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20status_name_by_id%20%3D%20%7B%7D%0A%20%20%20%20for%20workflow_status%20in%20workflow_statuses%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28workflow_status%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20status_id%20%3D%20workflow_status.get%28%22status_id%22%29%0A%20%20%20%20%20%20%20%20status%20%3D%20workflow_status.get%28%22status%22%29%0A%20%20%20%20%20%20%20%20status_name%20%3D%20status.get%28%22name%22%29%20if%20isinstance%28status%2C%20dict%29%20else%20None%0A%20%20%20%20%20%20%20%20if%20not%20is_finite_number%28status_id%29%20or%20not%20isinstance%28status_name%2C%20str%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20status_name%20%3D%20status_name.strip%28%29%0A%20%20%20%20%20%20%20%20if%20not%20status_name%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20existing_name%20%3D%20status_name_by_id.get%28status_id%29%0A%20%20%20%20%20%20%20%20if%20existing_name%20is%20not%20None%20and%20existing_name%20%21%3D%20status_name%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22workflow%20status%20ID%20%7Bstatus_id%21r%7D%20has%20conflicting%20status%20names%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20status_name_by_id%5Bstatus_id%5D%20%3D%20status_name%0A%0A%20%20%20%20status_names_by_task%20%3D%20%7B%7D%0A%20%20%20%20for%20task%20in%20config_tasks%3A%0A%20%20%20%20%20%20%20%20task_id%20%3D%20task%5B%22id%22%5D%0A%20%20%20%20%20%20%20%20task_status_names%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20status_id%20in%20task%5B%22statusValues%22%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status_name%20%3D%20status_name_by_id.get%28status_id%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20status_name%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22config%20task%20%7Btask_id%21r%7D%20field%20%5C%22statusValues%5C%22%20references%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22workflow%20status%20ID%20%7Bstatus_id%21r%7D%2C%20which%20is%20unavailable%20in%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22this%20project%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20status_name%20not%20in%20task_status_names%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20task_status_names.append%28status_name%29%0A%20%20%20%20%20%20%20%20status_names_by_task%5Btask_id%5D%20%3D%20task_status_names%0A%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22resolve_config_status_names%22%2C%0A%20%20%20%20%20%20%20%20task_status_names%3Dstatus_names_by_task%2C%0A%20%20%20%20%29%0A%20%20%20%20return%20status_names_by_task%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Payload%20validation%20helpers%0A%23%20---------------------------------------------------------------------------%0A%0Adef%20require_non_empty_string%28context%2C%20field%29%3A%0A%20%20%20%20value%20%3D%20context.get%28field%29%0A%20%20%20%20if%20not%20isinstance%28value%2C%20str%29%20or%20not%20value.strip%28%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20is%20required%20and%20must%20be%20a%20non-empty%20string%22%29%0A%20%20%20%20return%20value%0A%0A%0Adef%20require_id%28context%2C%20field%29%3A%0A%20%20%20%20value%20%3D%20context.get%28field%29%0A%20%20%20%20if%20isinstance%28value%2C%20bool%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20if%20isinstance%28value%2C%20int%29%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20value%0A%20%20%20%20elif%20isinstance%28value%2C%20float%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20math.isfinite%28value%29%20or%20not%20value.is_integer%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20%20%20%20%20result%20%3D%20int%28value%29%0A%20%20%20%20elif%20isinstance%28value%2C%20str%29%3A%0A%20%20%20%20%20%20%20%20text%20%3D%20value.strip%28%29%0A%20%20%20%20%20%20%20%20if%20not%20text.isascii%28%29%20or%20not%20text.isdecimal%28%29%20or%20text.startswith%28%220%22%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20%20%20%20%20result%20%3D%20int%28text%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20if%20result%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20return%20result%0A%0A%0Adef%20require_string_list%28context%2C%20field%2C%20description%29%3A%0A%20%20%20%20value%20%3D%20context.get%28field%29%0A%20%20%20%20if%20not%20isinstance%28value%2C%20list%29%20or%20not%20all%28%0A%20%20%20%20%20%20%20%20isinstance%28entry%2C%20str%29%20and%20entry.strip%28%29%20for%20entry%20in%20value%0A%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20is%20required%20and%20must%20be%20a%20list%20of%20%7Bdescription%7D%22%29%0A%20%20%20%20return%20value%0A%0A%0Adef%20is_finite_number%28value%29%3A%0A%20%20%20%20return%20%28%0A%20%20%20%20%20%20%20%20not%20isinstance%28value%2C%20bool%29%0A%20%20%20%20%20%20%20%20and%20isinstance%28value%2C%20%28int%2C%20float%29%29%0A%20%20%20%20%20%20%20%20and%20%28not%20isinstance%28value%2C%20float%29%20or%20math.isfinite%28value%29%29%0A%20%20%20%20%29%0A%0A%0Adef%20is_positive_whole_number%28value%29%3A%0A%20%20%20%20if%20not%20is_finite_number%28value%29%3A%0A%20%20%20%20%20%20%20%20return%20False%0A%20%20%20%20if%20isinstance%28value%2C%20float%29%20and%20not%20value.is_integer%28%29%3A%0A%20%20%20%20%20%20%20%20return%20False%0A%20%20%20%20return%20value%20%3E%200%0A%0A%0Adef%20is_valid_maximum_requested_items%28value%29%3A%0A%20%20%20%20return%20%28%0A%20%20%20%20%20%20%20%20is_positive_whole_number%28value%29%0A%20%20%20%20%20%20%20%20and%20value%20%3C%3D%20MAXIMUM_REQUESTED_ITEMS_MAX%0A%20%20%20%20%29%0A%0A%0Adef%20validate_request_config%28config_data%29%3A%0A%20%20%20%20%22%22%22Validate%20every%20assignment-affecting%20C1%20task-request%20setting.%0A%0A%20%20%20%20Configuration%20errors%20are%20deliberately%20distinct%20from%20bad/replayed%20request%0A%20%20%20%20payloads%3A%20the%20action%20wrapper%20re-raises%20them%20so%20a%20malformed%20published%0A%20%20%20%20configuration%20is%20visible%20as%20a%20failed%20Orchestrate%20execution.%0A%20%20%20%20%22%22%22%0A%20%20%20%20if%20not%20isinstance%28config_data%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22config%20key%20%7BCONFIG_DATA_KEY%21r%7D%20must%20be%20present%20and%20have%20an%20object%20value%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20tasks%20%3D%20config_data.get%28%22tasks%22%29%0A%20%20%20%20if%20not%20isinstance%28tasks%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%27config%20field%20%22tasks%22%20must%20be%20present%20and%20have%20a%20list%20value%27%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20task_ids%20%3D%20set%28%29%0A%20%20%20%20for%20index%2C%20task%20in%20enumerate%28tasks%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28task%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28f%22config%20task%20at%20index%20%7Bindex%7D%20must%20be%20an%20object%22%29%0A%0A%20%20%20%20%20%20%20%20task_id%20%3D%20task.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28task_id%2C%20str%29%20or%20not%20task_id.strip%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%27config%20task%20at%20index%20%7Bindex%7D%20must%20have%20a%20non-empty%20%22id%22%20text%20value%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20if%20task_id%20in%20task_ids%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28f%22component%20config%20has%20duplicate%20task%20id%20%7Btask_id%21r%7D%22%29%0A%20%20%20%20%20%20%20%20task_ids.add%28task_id%29%0A%0A%20%20%20%20%20%20%20%20items_per_request%20%3D%20task.get%28%22itemsPerRequest%22%29%0A%20%20%20%20%20%20%20%20if%20not%20is_positive_whole_number%28items_per_request%29%20or%20not%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20ITEMS_PER_REQUEST_MIN%20%3C%3D%20int%28items_per_request%29%20%3C%3D%20ITEMS_PER_REQUEST_MAX%0A%20%20%20%20%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22config%20task%20%7Btask_id%21r%7D%20itemsPerRequest%20must%20be%20a%20whole%20number%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22between%20%7BITEMS_PER_REQUEST_MIN%7D%20and%20%7BITEMS_PER_REQUEST_MAX%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%20%20%20%20status_values%20%3D%20task.get%28%22statusValues%22%29%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28status_values%2C%20list%29%20or%20not%20all%28%0A%20%20%20%20%20%20%20%20%20%20%20%20is_finite_number%28value%29%20for%20value%20in%20status_values%0A%20%20%20%20%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%27config%20task%20%7Btask_id%21r%7D%20field%20%22statusValues%22%20must%20be%20a%20list%20%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22of%20numeric%20status%20IDs%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%20%20%20%20if%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22maximumRequestedItems%22%20in%20task%0A%20%20%20%20%20%20%20%20%20%20%20%20and%20not%20is_valid_maximum_requested_items%28task%5B%22maximumRequestedItems%22%5D%29%0A%20%20%20%20%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22config%20task%20%7Btask_id%21r%7D%20maximumRequestedItems%20must%20be%20a%20positive%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22whole%20number%20no%20greater%20than%20%7BMAXIMUM_REQUESTED_ITEMS_MAX%7D%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22when%20present%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20for%20field%20in%20%28%22selectByCategory%22%2C%20%22pauseAllTaskRequests%22%29%3A%0A%20%20%20%20%20%20%20%20if%20field%20in%20config_data%20and%20not%20isinstance%28config_data%5Bfield%5D%2C%20bool%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28f%22component%20config%20%7Bfield%7D%20must%20be%20a%20boolean%22%29%0A%0A%20%20%20%20if%20%22excludedFolderIds%22%20in%20config_data%3A%0A%20%20%20%20%20%20%20%20folder_ids%20%3D%20config_data%5B%22excludedFolderIds%22%5D%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28folder_ids%2C%20list%29%20or%20not%20all%28%0A%20%20%20%20%20%20%20%20%20%20%20%20is_positive_whole_number%28folder_id%29%20for%20folder_id%20in%20folder_ids%0A%20%20%20%20%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27component%20configuration%20field%20%22excludedFolderIds%22%20must%20be%20a%20list%20%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22of%20positive%20whole-number%20folder%20IDs%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20return%20tasks%0A%0A%0Adef%20get_excluded_folder_names%28client%2C%20project_id%2C%20config_data%2C%20run%29%3A%0A%20%20%20%20%22%22%22Resolve%20rename-safe%20config%20folder%20IDs%20to%20the%20names%20SAQuL%20indexes.%0A%0A%20%20%20%20The%20component%20persists%20IDs.%20Explore%27s%20folder%20predicate%20accepts%20names%20only%2C%0A%20%20%20%20so%20resolve%20them%20immediately%20before%20building%20the%20eligibility%20query.%20Missing%0A%20%20%20%20folders%20are%20stale%20config%20and%20are%20harmlessly%20ignored%3B%20an%20inability%20to%20list%0A%20%20%20%20folders%20fails%20closed%20rather%20than%20silently%20assigning%20from%20an%20exclusion.%0A%20%20%20%20%22%22%22%0A%20%20%20%20raw_ids%20%3D%20config_data.get%28%22excludedFolderIds%22%2C%20%5B%5D%29%0A%20%20%20%20if%20not%20isinstance%28raw_ids%2C%20list%29%20or%20not%20all%28%0A%20%20%20%20%20%20%20%20is_positive_whole_number%28folder_id%29%20for%20folder_id%20in%20raw_ids%0A%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20raise%20ConfigurationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%27component%20configuration%20field%20%22excludedFolderIds%22%20must%20be%20a%20list%20%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%22of%20positive%20whole-number%20folder%20IDs%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20folder_ids%20%3D%20list%28dict.fromkeys%28int%28folder_id%29%20for%20folder_id%20in%20raw_ids%29%29%0A%0A%20%20%20%20if%20not%20folder_ids%3A%0A%20%20%20%20%20%20%20%20run.record%28%22folder_exclusions%22%2C%20folder_ids%3D%5B%5D%2C%20folder_names%3D%5B%5D%2C%20stale_folder_ids%3D%5B%5D%29%0A%20%20%20%20%20%20%20%20return%20%5B%5D%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20folders%20%3D%20client.list_folders%28project_id%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20list%20folders%20for%20exclusion%20in%20project%20%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%0A%20%20%20%20folder_names_by_id%20%3D%20%7B%7D%0A%20%20%20%20if%20isinstance%28folders%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20for%20folder%20in%20folders%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20isinstance%28folder%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20folder_id%20%3D%20folder.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20folder_name%20%3D%20folder.get%28%22name%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20not%20isinstance%28folder_id%2C%20bool%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20isinstance%28folder_id%2C%20%28int%2C%20float%29%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20float%28folder_id%29.is_integer%28%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20isinstance%28folder_name%2C%20str%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20folder_name.strip%28%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20folder_names_by_id%5Bint%28folder_id%29%5D%20%3D%20folder_name%0A%0A%20%20%20%20folder_names%20%3D%20%5B%0A%20%20%20%20%20%20%20%20folder_names_by_id%5Bfolder_id%5D%0A%20%20%20%20%20%20%20%20for%20folder_id%20in%20folder_ids%0A%20%20%20%20%20%20%20%20if%20folder_id%20in%20folder_names_by_id%0A%20%20%20%20%5D%0A%20%20%20%20stale_folder_ids%20%3D%20%5B%0A%20%20%20%20%20%20%20%20folder_id%20for%20folder_id%20in%20folder_ids%20if%20folder_id%20not%20in%20folder_names_by_id%0A%20%20%20%20%5D%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22folder_exclusions%22%2C%0A%20%20%20%20%20%20%20%20folder_ids%3Dfolder_ids%2C%0A%20%20%20%20%20%20%20%20folder_names%3Dfolder_names%2C%0A%20%20%20%20%20%20%20%20stale_folder_ids%3Dstale_folder_ids%2C%0A%20%20%20%20%29%0A%20%20%20%20return%20folder_names%0A%0A%0Adef%20build_eligibility_query%28%0A%20%20%20%20user_id%2C%0A%20%20%20%20statuses%2C%0A%20%20%20%20categories%2C%0A%20%20%20%20select_by_category%2C%0A%20%20%20%20excluded_folder_names%2C%0A%29%3A%0A%20%20%20%20%22%22%22Build%20the%20single%20Explore%20query%20used%20to%20source%20assignment%20candidates.%22%22%22%0A%20%20%20%20query%20%3D%20%28%0A%20%20%20%20%20%20%20%20f%27%28web_%7BUSER_PROFILE_COMPONENT_ID%7D%28users%20NOT%20CONTAINS%20%22%7Buser_id%7D%22%29%27%0A%20%20%20%20%20%20%20%20f%22%20OR%20web_%7BUSER_PROFILE_COMPONENT_ID%7D%28users%20%3D%20none%29%29%22%0A%20%20%20%20%20%20%20%20f%22%20AND%20%28web_%7BUSER_PROFILE_COMPONENT_ID%7D%28userProfile%20%21%3D%20true%29%22%0A%20%20%20%20%20%20%20%20f%22%20OR%20web_%7BUSER_PROFILE_COMPONENT_ID%7D%28userProfile%20%3D%20none%29%29%22%0A%20%20%20%20%20%20%20%20f%22%20AND%20metadata%28status%20IN%20%7Bjson.dumps%28statuses%29%7D%29%22%0A%20%20%20%20%20%20%20%20%22%20AND%20assignment.Contributor%28email%20%3D%20none%29%22%0A%20%20%20%20%29%0A%20%20%20%20if%20select_by_category%3A%0A%20%20%20%20%20%20%20%20query%20%2B%3D%20f%22%20AND%20metadata%28category%20IN%20%7Bjson.dumps%28categories%29%7D%29%22%0A%20%20%20%20if%20excluded_folder_names%3A%0A%20%20%20%20%20%20%20%20query%20%2B%3D%20f%22%20AND%20folderName%20NOT%20IN%20%7Bjson.dumps%28excluded_folder_names%29%7D%22%0A%20%20%20%20return%20query%0A%0A%0Adef%20build_assigned_items_query%28user_id%2C%20statuses%29%3A%0A%20%20%20%20%22%22%22Build%20the%20Explore%20query%20for%20the%20selected%20task%27s%20assigned-item%20cap.%22%22%22%0A%20%20%20%20return%20%28%0A%20%20%20%20%20%20%20%20f%27assignment.Contributor%28email%20%3D%20%22%7Buser_id%7D%22%29%27%0A%20%20%20%20%20%20%20%20f%22%20AND%20%28web_%7BUSER_PROFILE_COMPONENT_ID%7D%28userProfile%20%21%3D%20true%29%22%0A%20%20%20%20%20%20%20%20f%22%20OR%20web_%7BUSER_PROFILE_COMPONENT_ID%7D%28userProfile%20%3D%20none%29%29%22%0A%20%20%20%20%20%20%20%20f%22%20AND%20metadata%28status%20IN%20%7Bjson.dumps%28statuses%29%7D%29%22%0A%20%20%20%20%29%0A%0A%0Adef%20item_context_snapshot%28component_value%29%3A%0A%20%20%20%20%22%22%22Return%20a%20diagnostic-safe%20profile-value%20snapshot.%0A%0A%20%20%20%20Request%20history%20can%20grow%20without%20bound%20on%20a%20long-lived%20profile.%20Keep%20the%0A%20%20%20%20rest%20of%20the%20item%20context%20for%20run%20diagnostics%20without%20changing%20the%20original%0A%20%20%20%20value%20that%20was%20read%20from%20the%20platform.%0A%20%20%20%20%22%22%22%0A%20%20%20%20if%20not%20isinstance%28component_value%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20return%20component_value%0A%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20key%3A%20value%20for%20key%2C%20value%20in%20component_value.items%28%29%0A%20%20%20%20%20%20%20%20if%20key%20%21%3D%20%22requestHistory%22%0A%20%20%20%20%7D%0A%0A%0Adef%20get_maximum_requested_items%28config_task%29%3A%0A%20%20%20%20%22%22%22Return%20a%20validated%20task%27s%20optional%20lifetime%20allocation%20limit.%22%22%22%0A%20%20%20%20value%20%3D%20config_task.get%28%22maximumRequestedItems%22%29%0A%20%20%20%20return%20None%20if%20value%20is%20None%20else%20int%28value%29%0A%0A%0Adef%20count_requested_items_for_task%28component_value%2C%20task_id%29%3A%0A%20%20%20%20%22%22%22Count%20actual%20prior%20allocations%20for%20one%20task%20from%20profile%20history.%22%22%22%0A%20%20%20%20if%20not%20isinstance%28component_value%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20history%20%3D%20component_value.get%28%22requestHistory%22%29%0A%20%20%20%20if%20not%20isinstance%28history%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20return%200%0A%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20entry%20in%20history%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28entry%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20if%20entry.get%28%22taskId%22%29%20%21%3D%20task_id%20or%20entry.get%28%22ok%22%29%20is%20not%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20item_ids%20%3D%20entry.get%28%22itemIds%22%29%0A%20%20%20%20%20%20%20%20if%20isinstance%28item_ids%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%20len%28item_ids%29%0A%20%20%20%20return%20count%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Profile%20helpers%0A%23%20---------------------------------------------------------------------------%0A%0Adef%20is_user_profile%28component_value%29%3A%0A%20%20%20%20if%20not%20isinstance%28component_value%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20return%20False%0A%20%20%20%20if%20component_value.get%28%22userProfile%22%29%20in%20%28%22true%22%2C%20%22false%22%29%3A%0A%20%20%20%20%20%20%20%20return%20component_value.get%28%22userProfile%22%29%20%3D%3D%20%22true%22%0A%20%20%20%20%23%20Items%20written%20before%20the%20string%20migration%20carry%20a%20boolean.%0A%20%20%20%20return%20component_value.get%28%22isUserProfile%22%29%20is%20True%0A%0A%0Adef%20is_assigned_to%28item%2C%20user_id%29%3A%0A%20%20%20%20%22%22%22True%20when%20the%20item%27s%20assignments%20include%20the%20user%20%28matched%20by%0A%20%20%20%20user_id/user_email%2C%20case-insensitive%20%E2%80%94%20assignment%20ids%20are%20emails%29.%22%22%22%0A%20%20%20%20assignments%20%3D%20item.get%28%22assignments%22%29%20if%20isinstance%28item%2C%20dict%29%20else%20None%0A%20%20%20%20if%20not%20isinstance%28assignments%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20return%20False%0A%20%20%20%20wanted%20%3D%20user_id.strip%28%29.lower%28%29%0A%20%20%20%20for%20assignment%20in%20assignments%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28assignment%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20for%20key%20in%20%28%22user_id%22%2C%20%22user_email%22%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20value%20%3D%20assignment.get%28key%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28value%2C%20str%29%20and%20value.strip%28%29.lower%28%29%20%3D%3D%20wanted%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20True%0A%20%20%20%20return%20False%0A%0A%0Adef%20read_profile_annotation%28client%2C%20project_id%2C%20folder_id%2C%20item_id%29%3A%0A%20%20%20%20%22%22%22Component%20value%20%2B%20metadata%20for%20the%20item%2C%20hedging%20around%20an%20SDK%20bug%0A%20%20%20%20%28present%20in%204.5.5%20and%204.5.8%29%3A%20the%20annotation%20adapter%20always%20calls%0A%20%20%20%20json.loads%28response.data%29%2C%20but%20for%20some%20items%20the%20annotation%20service%0A%20%20%20%20returns%20an%20already-parsed%20dict%2C%20raising%20TypeError.%20In%20that%20case%2C%20seed%20the%0A%20%20%20%20adapter%27s%20cache%20with%20the%20parsed%20dict%20and%20retry.%20Read-only%20use%20%E2%80%94%20save%28%29%20is%0A%20%20%20%20never%20called%20here.%22%22%22%0A%20%20%20%20with%20client.item_context%28%28project_id%2C%20folder_id%29%2C%20item_id%29%20as%20item_ctx%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20item_ctx.get_component_value%28USER_PROFILE_COMPONENT_ID%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20item_ctx.get_metadata%28%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20except%20TypeError%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20adapter%20%3D%20getattr%28item_ctx%2C%20%22annotation_adapter%22%2C%20None%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adapter%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%0A%20%20%20%20%20%20%20%20%20%20%20%20response%20%3D%20adapter._controller.annotations.get_item_annotations%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20project%3Dadapter._project%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20folder%3Dadapter._folder%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20item_id%3Dadapter._item.id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20transform_version%3D%22llmJsonV3%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20data%20%3D%20getattr%28response%2C%20%22data%22%2C%20None%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28data%2C%20%28str%2C%20bytes%2C%20bytearray%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%20%3D%20json.loads%28data%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20isinstance%28data%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20An%20empty/partial%20dict%20means%20the%20item%20has%20no%20%28usable%29%20stored%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20annotation%3B%20normalize%20to%20the%20adapter%27s%20own%20no-annotation%20shape%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20so%20metadata%20access%20works%20and%20the%20component%20value%20reads%20as%20None.%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20%22metadata%22%20not%20in%20data%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22metadata%22%3A%20%7B%22id%22%3A%20adapter._item.id%2C%20%22name%22%3A%20adapter._item.name%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22data%22%3A%20data.get%28%22data%22%2C%20%7B%7D%29%20if%20isinstance%28data.get%28%22data%22%29%2C%20dict%29%20else%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20adapter._annotation%20%3D%20data%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20item_ctx.get_component_value%28USER_PROFILE_COMPONENT_ID%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20item_ctx.get_metadata%28%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Per-project%2Btask%20assignment%20lock%20%28DynamoDB%29%0A%23%20---------------------------------------------------------------------------%0A%0Adef%20acquire_request_lock%28ddb%2C%20lock_id%2C%20run%29%3A%0A%20%20%20%20%22%22%22Take%20the%20per-project%2Btask%20assignment%20lock%20%28DynamoDB%20conditional%20put%29.%0A%20%20%20%20Returns%20the%20owner%20token%20needed%20to%20release.%20An%20expired%20lease%20%28crashed%0A%20%20%20%20holder%29%20counts%20as%20free.%20Failure%20behavior%3A%0A%20%20%20%20-%20held%20by%20someone%20else%3A%20poll%20until%20LOCK_ACQUIRE_TIMEOUT_SEC%2C%20then%20raise%20%E2%80%94%0A%20%20%20%20%20%20proceeding%20would%20recreate%20the%20double-assignment%20race%3B%0A%20%20%20%20-%20any%20lock%20infrastructure%20failure%20records%20a%20failed%20lock%20operation%20and%0A%20%20%20%20%20%20raises%20before%20assignment%20work%20begins.%22%22%22%0A%20%20%20%20owner%20%3D%20str%28uuid.uuid4%28%29%29%0A%20%20%20%20started%20%3D%20time.time%28%29%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20now%20%3D%20int%28time.time%28%29%29%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ddb.put_item%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20TableName%3DLOCKS_TABLE_NAME%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Item%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22lock_id%22%3A%20%7B%22S%22%3A%20lock_id%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22owner%22%3A%20%7B%22S%22%3A%20owner%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22expires_at%22%3A%20%7B%22N%22%3A%20str%28now%20%2B%20LOCK_LEASE_SEC%29%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ConditionExpression%3D%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22attribute_not_exists%28lock_id%29%20OR%20expires_at%20%3C%20%3Anow%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ExpressionAttributeValues%3D%7B%22%3Anow%22%3A%20%7B%22N%22%3A%20str%28now%29%7D%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20except%20ddb.exceptions.ConditionalCheckFailedException%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20time.time%28%29%20-%20started%20%3E%3D%20LOCK_ACQUIRE_TIMEOUT_SEC%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20message%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22this%20task%20is%20busy%20handling%20other%20requests%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%28lock%20%7Block_id%7D%20not%20acquired%20within%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7BLOCK_ACQUIRE_TIMEOUT_SEC%7Ds%29%3B%20please%20try%20again%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%22lock%22%2C%20ok%3DFalse%2C%20lock_id%3Dlock_id%2C%20error%3Dmessage%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20LockContentionError%28message%29%0A%20%20%20%20%20%20%20%20%20%20%20%20time.sleep%28LOCK_POLL_SEC%29%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20message%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22could%20not%20acquire%20DynamoDB%20request%20lock%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%28%7Btype%28error%29.__name__%7D%29%3B%20verify%20the%20lock%20table%2C%20IAM%20permissions%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22and%20AWS%20credentials%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20log%28f%22Lock%20acquisition%20failed%20for%20%7Block_id%7D%3A%20%7Bmessage%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%22lock%22%2C%20ok%3DFalse%2C%20lock_id%3Dlock_id%2C%20error%3Dmessage%29%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20LockInfrastructureError%28message%29%20from%20error%0A%20%20%20%20%20%20%20%20waited%20%3D%20round%28time.time%28%29%20-%20started%2C%202%29%0A%20%20%20%20%20%20%20%20log%28f%22Acquired%20%7Block_id%7D%20after%20%7Bwaited%7Ds%22%29%0A%20%20%20%20%20%20%20%20run.record%28%22lock%22%2C%20lock_id%3Dlock_id%2C%20waited_sec%3Dwaited%29%0A%20%20%20%20%20%20%20%20return%20owner%0A%0A%0Adef%20get_dynamodb_lock_client%28lock_id%2C%20run%29%3A%0A%20%20%20%20%22%22%22Return%20an%20explicitly%20configured%20DynamoDB%20client%20or%20fail%20closed.%22%22%22%0A%20%20%20%20required_settings%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22AWS_ACCESS_KEY_ID%22%3A%20AWS_ACCESS_KEY_ID%2C%0A%20%20%20%20%20%20%20%20%22AWS_SECRET_ACCESS_KEY%22%3A%20AWS_SECRET_ACCESS_KEY%2C%0A%20%20%20%20%20%20%20%20%22AWS_DEFAULT_REGION%22%3A%20AWS_DEFAULT_REGION%2C%0A%20%20%20%20%7D%0A%20%20%20%20missing%20%3D%20%5B%0A%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%20%20for%20name%2C%20value%20in%20required_settings.items%28%29%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28value%2C%20str%29%20or%20not%20value.strip%28%29%0A%20%20%20%20%5D%0A%20%20%20%20if%20missing%3A%0A%20%20%20%20%20%20%20%20message%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22DynamoDB%20request%20lock%20is%20not%20configured%3A%20missing%20or%20blank%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20%22%2C%20%22.join%28missing%29%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20run.record%28%22lock%22%2C%20ok%3DFalse%2C%20lock_id%3Dlock_id%2C%20error%3Dmessage%29%0A%20%20%20%20%20%20%20%20raise%20LockInfrastructureError%28message%29%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20return%20boto3.client%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22dynamodb%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20aws_access_key_id%3DAWS_ACCESS_KEY_ID%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20aws_secret_access_key%3DAWS_SECRET_ACCESS_KEY%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20region_name%3DAWS_DEFAULT_REGION%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20message%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22could%20not%20create%20DynamoDB%20request%20lock%20client%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%28%7Btype%28error%29.__name__%7D%29%3B%20verify%20AWS%20credentials%20and%20region%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20log%28f%22Lock%20client%20creation%20failed%20for%20%7Block_id%7D%3A%20%7Bmessage%7D%22%29%0A%20%20%20%20%20%20%20%20run.record%28%22lock%22%2C%20ok%3DFalse%2C%20lock_id%3Dlock_id%2C%20error%3Dmessage%29%0A%20%20%20%20%20%20%20%20raise%20LockInfrastructureError%28message%29%20from%20error%0A%0A%0Adef%20release_request_lock%28ddb%2C%20lock_id%2C%20owner%2C%20run%29%3A%0A%20%20%20%20%22%22%22Free%20the%20lock%20if%20we%20still%20own%20it.%20Never%20raises%3A%20on%20any%20failure%20the%0A%20%20%20%20lease%20expiry%20%28LOCK_LEASE_SEC%29%20frees%20the%20lock%20eventually.%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20ddb.delete_item%28%0A%20%20%20%20%20%20%20%20%20%20%20%20TableName%3DLOCKS_TABLE_NAME%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Key%3D%7B%22lock_id%22%3A%20%7B%22S%22%3A%20lock_id%7D%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ConditionExpression%3D%22%23o%20%3D%20%3Aowner%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ExpressionAttributeNames%3D%7B%22%23o%22%3A%20%22owner%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ExpressionAttributeValues%3D%7B%22%3Aowner%22%3A%20%7B%22S%22%3A%20owner%7D%7D%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20log%28f%22Released%20%7Block_id%7D%22%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20message%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20release%20DynamoDB%20request%20lock%20%28%7Btype%28error%29.__name__%7D%29%3B%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22the%20120-second%20lease%20will%20expire%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20log%28f%22Lock%20release%20failed%20for%20%7Block_id%7D%3A%20%7Bmessage%7D%22%29%0A%20%20%20%20%20%20%20%20run.record%28%22release_lock%22%2C%20ok%3DFalse%2C%20lock_id%3Dlock_id%2C%20error%3Dmessage%29%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Handler%0A%23%20---------------------------------------------------------------------------%0A%0A%40action%0Adef%20handler%28event%2C%20context%2C%20run%29%3A%0A%20%20%20%20user_id%20%3D%20require_non_empty_string%28context%2C%20%22user_id%22%29%0A%20%20%20%20item_name%20%3D%20require_non_empty_string%28context%2C%20%22item_name%22%29%0A%20%20%20%20item_id%20%3D%20require_id%28context%2C%20%22item_id%22%29%0A%20%20%20%20project_id%20%3D%20require_id%28context%2C%20%22project_id%22%29%0A%20%20%20%20folder_id%20%3D%20require_id%28context%2C%20%22folder_id%22%29%0A%0A%20%20%20%20tasks%20%3D%20require_string_list%28context%2C%20%22tasks%22%2C%20%22task%20ids%22%29%0A%20%20%20%20%23%20The%20component%20UI%20is%20single-select%3A%20the%20payload%27s%20tasks%20array%20always%0A%20%20%20%20%23%20carries%20exactly%20one%20task%20id.%20The%20per-project%2Btask%20lock%20key%20relies%20on%0A%20%20%20%20%23%20this%2C%20so%20enforce%20it%20instead%20of%20silently%20locking%20on%20tasks%5B0%5D%20only.%0A%20%20%20%20if%20len%28tasks%29%20%21%3D%201%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22exactly%20one%20task%20must%20be%20requested%2C%20got%20%7Blen%28tasks%29%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%23%20Category%20NAMES%20the%20contributor%20picked.%20Optional%20in%20the%20payload%20%28empty%0A%20%20%20%20%23%20when%20the%20feature%20is%20off%29%3B%20whether%20at%20least%20one%20is%20REQUIRED%20is%20decided%0A%20%20%20%20%23%20by%20the%20config%27s%20selectByCategory%20flag%2C%20checked%20after%20the%20config%20read.%0A%20%20%20%20categories%20%3D%20context.get%28%22categories%22%29%0A%20%20%20%20if%20categories%20is%20None%3A%0A%20%20%20%20%20%20%20%20categories%20%3D%20%5B%5D%0A%20%20%20%20if%20not%20isinstance%28categories%2C%20list%29%20or%20not%20all%28%0A%20%20%20%20%20%20%20%20isinstance%28category%2C%20str%29%20and%20category.strip%28%29%20for%20category%20in%20categories%0A%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%22categories%20must%20be%20a%20list%20of%20category%20names%22%29%0A%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22validate%22%2C%0A%20%20%20%20%20%20%20%20user_id%3Duser_id%2C%0A%20%20%20%20%20%20%20%20item_id%3Ditem_id%2C%0A%20%20%20%20%20%20%20%20task_count%3Dlen%28tasks%29%2C%0A%20%20%20%20%20%20%20%20categories%3Dcategories%2C%0A%20%20%20%20%29%0A%0A%20%20%20%20client%20%3D%20get_sa_client%28%29%0A%20%20%20%20team_id%20%3D%20get_team_id%28client%29%0A%0A%20%20%20%20%23%201.%20Read%20the%20item%20and%20make%20sure%20the%20payload%20is%20talking%20about%20it.%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20item%20%3D%20client.get_item_by_id%28project_id%2C%20item_id%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20item%20%7Bitem_id%7D%20in%20project%20%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20actual_name%20%3D%20item.get%28%22name%22%29%20if%20isinstance%28item%2C%20dict%29%20else%20getattr%28item%2C%20%22name%22%2C%20None%29%0A%20%20%20%20if%20actual_name%20%21%3D%20item_name%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22item%20name%20mismatch%3A%20payload%20says%20%7Bitem_name%21r%7D%2C%20item%20%7Bitem_id%7D%20is%20%7Bactual_name%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20run.record%28%22read_item%22%2C%20item_id%3Ditem_id%2C%20name%3Dactual_name%29%0A%0A%20%20%20%20%23%202.%20Verify%20it%20is%20a%20user-profile%20item%20via%20the%20component%27s%20per-item%20value.%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20component_value%2C%20metadata%20%3D%20read_profile_annotation%28%0A%20%20%20%20%20%20%20%20%20%20%20%20client%2C%20project_id%2C%20folder_id%2C%20item_id%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20item%20context%20for%20item%20%7Bitem_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20if%20not%20is_user_profile%28component_value%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22item%20%7Bitem_id%7D%20%28%7Bitem_name%21r%7D%29%20is%20not%20a%20user%20profile%20item%22%29%0A%20%20%20%20run.record%28%22verify_profile%22%2C%20item_id%3Ditem_id%29%0A%0A%20%20%20%20%23%203.%20The%20profile%20item%20must%20already%20be%20assigned%20to%20the%20requesting%20user%20%E2%80%94%0A%20%20%20%20%23%20that%20assignment%20is%20what%20grants%20them%20access%20in%20the%20first%20place.%20Do%20NOT%0A%20%20%20%20%23%20assign%20here%3B%20just%20verify%20the%20request%20comes%20from%20the%20item%27s%20owner.%0A%20%20%20%20if%20not%20is_assigned_to%28item%2C%20user_id%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22item%20%7Bitem_name%21r%7D%20is%20not%20assigned%20to%20%7Buser_id%7D%3B%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22task%20requests%20must%20come%20from%20the%20profile%27s%20own%20user%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20run.record%28%22verify_assignment%22%2C%20item%3Ditem_name%2C%20user_id%3Duser_id%29%0A%0A%20%20%20%20%23%204.%20Read%20the%20component%20config%3A%20the%20requested%20task%20must%20exist%20in%20it%2C%20and%0A%20%20%20%20%23%20it%20drives%20how%20many%20items%20the%20requested%20task%28s%29%20entitle%20the%20user%20to%2C%20the%0A%20%20%20%20%23%20statuses%20they%20operate%20on%2C%20and%20whether%20categories%20are%20required.%20It%20does%0A%20%20%20%20%23%20NOT%20decide%20access%20any%20more%20%E2%80%94%20see%20step%205.%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20config%20%3D%20client.get_component_config%28project_id%2C%20USER_PROFILE_COMPONENT_ID%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20component%20config%20for%20project%20%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20config_data%20%3D%20config.get%28CONFIG_DATA_KEY%29%20if%20isinstance%28config%2C%20dict%29%20else%20None%0A%20%20%20%20config_tasks%20%3D%20validate_request_config%28config_data%29%0A%20%20%20%20status_names_by_task%20%3D%20resolve_config_status_names%28%0A%20%20%20%20%20%20%20%20client%2C%20project_id%2C%20config_tasks%2C%20run%0A%20%20%20%20%29%0A%0A%20%20%20%20%23%20Enforce%20request%20pauses%20server-side.%20The%20component%27s%20controls%20are%20only%20a%0A%20%20%20%20%23%20convenience%20layer%3A%20webhook%20payloads%20can%20be%20forged%20or%20replayed%20after%20the%0A%20%20%20%20%23%20config/item%20state%20has%20changed.%20Only%20an%20explicit%20boolean%20True%20pauses%3B%0A%20%20%20%20%23%20missing%2C%20null%2C%20and%20malformed%20legacy%20values%20remain%20unpaused.%0A%20%20%20%20project_paused%20%3D%20%28%0A%20%20%20%20%20%20%20%20isinstance%28config_data%2C%20dict%29%0A%20%20%20%20%20%20%20%20and%20config_data.get%28%22pauseAllTaskRequests%22%29%20is%20True%0A%20%20%20%20%29%0A%20%20%20%20user_paused%20%3D%20%28%0A%20%20%20%20%20%20%20%20isinstance%28component_value%2C%20dict%29%0A%20%20%20%20%20%20%20%20and%20component_value.get%28%22pauseUser%22%29%20is%20True%0A%20%20%20%20%29%0A%20%20%20%20request_rejected%20%3D%20project_paused%20or%20user_paused%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22verify_request_pauses%22%2C%0A%20%20%20%20%20%20%20%20project_paused%3Dproject_paused%2C%0A%20%20%20%20%20%20%20%20user_paused%3Duser_paused%2C%0A%20%20%20%20%20%20%20%20permitted%3Dnot%20request_rejected%2C%0A%20%20%20%20%20%20%20%20rejected%3Drequest_rejected%2C%0A%20%20%20%20%29%0A%20%20%20%20if%20project_paused%3A%0A%20%20%20%20%20%20%20%20raise%20RequestStateError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22project_requests_paused%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22task%20requests%20are%20paused%20for%20this%20project%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20if%20user_paused%3A%0A%20%20%20%20%20%20%20%20raise%20RequestStateError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22user_requests_paused%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22task%20requests%20are%20paused%20for%20this%20user%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20config_tasks_by_id%20%3D%20%7B%0A%20%20%20%20%20%20%20%20task%5B%22id%22%5D%3A%20task%20for%20task%20in%20config_tasks%0A%20%20%20%20%7D%0A%0A%20%20%20%20%23%205.%20Every%20requested%20task%20must%20be%20granted%20on%20THIS%20profile%20item.%20The%0A%20%20%20%20%23%20item%27s%20allowedTaskIds%20is%20the%20single%20source%20of%20truth%3A%20the%20config%27s%0A%20%20%20%20%23%20allowedByDefault%20flag%20is%20a%20creation-time%20seed%20written%20once%20by%0A%20%20%20%20%23%20smart_flow_0__user_profile_handler%2C%20so%20it%20grants%20nothing%20here%20and%0A%20%20%20%20%23%20changing%20it%20never%20affects%20a%20profile%20that%20already%20exists.%20The%20per-profile%0A%20%20%20%20%23%20deny%20list%20that%20older%20versions%20wrote%20is%20no%20longer%20part%20of%20the%20contract%3A%0A%20%20%20%20%23%20values%20still%20stored%20on%20old%20items%20are%20inert%20and%20deliberately%20ignored.%0A%20%20%20%20%23%20A%20missing/non-list%20allowedTaskIds%20means%20no%20grants.%0A%20%20%20%20allowed_task_ids%20%3D%20component_value.get%28%22allowedTaskIds%22%29%0A%20%20%20%20if%20not%20isinstance%28allowed_task_ids%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20allowed_task_ids%20%3D%20%5B%5D%0A%20%20%20%20requested_task_id%20%3D%20tasks%5B0%5D%0A%20%20%20%20task_is_allowed%20%3D%20requested_task_id%20in%20allowed_task_ids%0A%20%20%20%20task_exists_in_config%20%3D%20requested_task_id%20in%20config_tasks_by_id%0A%20%20%20%20if%20not%20task_is_allowed%20or%20not%20task_exists_in_config%3A%0A%20%20%20%20%20%20%20%20reason%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22is%20no%20longer%20available%20on%20this%20profile%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20task_is_allowed%0A%20%20%20%20%20%20%20%20%20%20%20%20else%20%22no%20longer%20exists%20in%20the%20component%20config%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20raise%20RequestStateError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22available_tasks_changed%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22requested%20task%20%7Brequested_task_id%21r%7D%20%7Breason%7D%3B%20refresh%20and%20try%20again%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20run.record%28%22verify_allowed_tasks%22%2C%20tasks%3Dtasks%2C%20allowed%3Dallowed_task_ids%29%0A%0A%20%20%20%20%23%205a.%20The%20requested%20task%20passed%20the%20single-task%20config%20check%20above.%0A%20%20%20%20%23%20Accumulate%20its%20item%20entitlement%20and%20record%20attached%20statuses.%0A%20%20%20%20items_to_assign%20%3D%200%0A%20%20%20%20attached_statuses_by_task%20%3D%20%7B%7D%0A%20%20%20%20for%20task_id%20in%20tasks%3A%0A%20%20%20%20%20%20%20%20config_task%20%3D%20config_tasks_by_id.get%28task_id%29%0A%20%20%20%20%20%20%20%20items_to_assign%20%2B%3D%20int%28config_task%5B%22itemsPerRequest%22%5D%29%0A%0A%20%20%20%20%20%20%20%20%23%20%22Attached%20status%22%20column%20of%20the%20config%20table%3A%20the%20workflow-status%0A%20%20%20%20%20%20%20%20%23%20ids%20this%20task%20operates%20on.%0A%20%20%20%20%20%20%20%20status_values%20%3D%20config_task%5B%22statusValues%22%5D%0A%20%20%20%20%20%20%20%20attached_statuses_by_task%5Btask_id%5D%20%3D%20status_values%0A%20%20%20%20%20%20%20%20log%28f%22Attached%20status%20for%20task%20%7Btask_id%21r%7D%3A%20%7Bstatus_values%7D%22%29%0A%20%20%20%20%20%20%20%20run.record%28%22attached_status%22%2C%20task%3Dtask_id%2C%20status_values%3Dstatus_values%29%0A%0A%20%20%20%20statuses%20%3D%20status_names_by_task%5Btasks%5B0%5D%5D%0A%20%20%20%20log%28f%22Resolved%20status%20names%20for%20task%20%7Btasks%5B0%5D%21r%7D%3A%20%7Bstatuses%7D%22%29%0A%20%20%20%20log%28f%22Number%20of%20items%20to%20be%20assigned%3A%20%7Bitems_to_assign%7D%22%29%0A%20%20%20%20run.record%28%22items_to_assign%22%2C%20count%3Ditems_to_assign%2C%20tasks%3Dtasks%29%0A%0A%20%20%20%20%23%205b.%20Cumulative%20allocation%20maximum%3A%20profile%20requestHistory%20is%20the%0A%20%20%20%20%23%20authoritative%20record%20of%20actual%20past%20assignments%20for%20this%20purpose.%20A%0A%20%20%20%20%23%20reached%20maximum%20returns%20a%20handled%20RequestStateError%20with%0A%20%20%20%20%23%20error_code%3D%22maximum_requested_items_reached%22%20before%20lock/query/assignment%0A%20%20%20%20%23%20work%20begins%2C%20letting%20the%20component%20report%20the%20precise%20policy%20state.%0A%20%20%20%20requested_task_config%20%3D%20config_tasks_by_id%5Brequested_task_id%5D%0A%20%20%20%20maximum_requested_items%20%3D%20get_maximum_requested_items%28requested_task_config%29%0A%20%20%20%20historical_items_received%20%3D%20count_requested_items_for_task%28%0A%20%20%20%20%20%20%20%20component_value%2C%20requested_task_id%0A%20%20%20%20%29%0A%20%20%20%20maximum_items_remaining%20%3D%20%28%0A%20%20%20%20%20%20%20%20None%0A%20%20%20%20%20%20%20%20if%20maximum_requested_items%20is%20None%0A%20%20%20%20%20%20%20%20else%20max%280%2C%20maximum_requested_items%20-%20historical_items_received%29%0A%20%20%20%20%29%0A%20%20%20%20maximum_reached%20%3D%20maximum_items_remaining%20%3D%3D%200%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22maximum_requested_items%22%2C%0A%20%20%20%20%20%20%20%20task%3Drequested_task_id%2C%0A%20%20%20%20%20%20%20%20maximum%3Dmaximum_requested_items%2C%0A%20%20%20%20%20%20%20%20historical_items_received%3Dhistorical_items_received%2C%0A%20%20%20%20%20%20%20%20remaining%3Dmaximum_items_remaining%2C%0A%20%20%20%20%20%20%20%20permitted%3Dnot%20maximum_reached%2C%0A%20%20%20%20%20%20%20%20rejected%3Dmaximum_reached%2C%0A%20%20%20%20%29%0A%20%20%20%20if%20maximum_reached%3A%0A%20%20%20%20%20%20%20%20raise%20RequestStateError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22maximum_requested_items_reached%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22maximum%20requested%20items%20reached%20for%20%7Brequested_task_id%21r%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bhistorical_items_received%7D/%7Bmaximum_requested_items%7D%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%23%205c.%20Category-based%20requests%3A%20when%20enabled%20in%20the%20config%2C%20the%20request%0A%20%20%20%20%23%20must%20carry%20at%20least%20one%20category%2C%20and%20the%20eligibility%20query%20filters%0A%20%20%20%20%23%20by%20them%20%28see%20step%207%29.%0A%20%20%20%20select_by_category%20%3D%20%28%0A%20%20%20%20%20%20%20%20isinstance%28config_data%2C%20dict%29%20and%20config_data.get%28%22selectByCategory%22%29%20is%20True%0A%20%20%20%20%29%0A%20%20%20%20if%20select_by_category%20and%20not%20categories%3A%0A%20%20%20%20%20%20%20%20raise%20RequestStateError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22categories_now_required%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22select-by-category%20is%20enabled%3A%20at%20least%20one%20category%20must%20be%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22selected%20with%20the%20request%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20run.record%28%22category_mode%22%2C%20enabled%3Dselect_by_category%2C%20categories%3Dcategories%29%0A%20%20%20%20excluded_folder_names%20%3D%20get_excluded_folder_names%28%0A%20%20%20%20%20%20%20%20client%2C%20project_id%2C%20config_data%2C%20run%0A%20%20%20%20%29%0A%0A%20%20%20%20%23%20Steps%206-8%20run%20under%20the%20per-project%2Btask%20lock%3A%20assignment%20is%20last-wins%2C%0A%20%20%20%20%23%20so%20two%20concurrent%20handlers%20that%20both%20saw%20an%20item%20as%20free%20would%20silently%0A%20%20%20%20%23%20steal%20it%20from%20each%20other.%20Serializing%20the%20read-decide-assign%20section%0A%20%20%20%20%23%20removes%20the%20race.%20Scoping%20the%20lock%20to%20the%20task%20%28not%20just%20the%20project%29%20is%0A%20%20%20%20%23%20safe%20because%20task%20statuses%20are%20exclusive%20%28a%20status%20id%20belongs%20to%20at%20most%0A%20%20%20%20%23%20one%20task%29%2C%20so%20requests%20for%20different%20tasks%20never%20compete%20for%20the%20same%0A%20%20%20%20%23%20candidate%20items.%20The%20availability%20guard%20%28step%206%29%20is%20inside%20the%20lock%20so%0A%20%20%20%20%23%20two%20same-user%20requests%20for%20the%20SAME%20task%20cannot%20both%20pass%20the%20cap%20check%0A%20%20%20%20%23%20%28concurrent%20different-task%20requests%20by%20one%20user%20may%20still%20exceed%20the%0A%20%20%20%20%23%20global%20cap%20%E2%80%94%20accepted%20trade-off%20for%20cross-task%20throughput%29.%0A%20%20%20%20lock_id%20%3D%20f%22request%3A%7Bproject_id%7D%3A%7Btasks%5B0%5D%7D%22%0A%20%20%20%20ddb%20%3D%20get_dynamodb_lock_client%28lock_id%2C%20run%29%0A%20%20%20%20lock_owner%20%3D%20acquire_request_lock%28ddb%2C%20lock_id%2C%20run%29%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%23%206.%20Guard%3A%20count%20only%20the%20user%27s%20non-profile%20items%20in%20the%20selected%0A%20%20%20%20%20%20%20%20%23%20task%27s%20workflow%20statuses.%20items_to_assign%20is%20a%20per-task%20cap%2C%20so%0A%20%20%20%20%20%20%20%20%23%20assignments%20in%20another%20task%20%28or%20outside%20every%20configured%20task%29%20do%0A%20%20%20%20%20%20%20%20%23%20not%20consume%20it.%0A%20%20%20%20%20%20%20%20availability_query%20%3D%20build_assigned_items_query%28%0A%20%20%20%20%20%20%20%20%20%20%20%20user_id%2C%20statuses%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20log%28f%22Available-items%20query%3A%20%7Bavailability_query%7D%22%29%0A%20%20%20%20%20%20%20%20available_items%20%3D%20query_items%28%0A%20%20%20%20%20%20%20%20%20%20%20%20project_id%2C%20availability_query%2C%20limit%3Ditems_to_assign%2C%20team_id%3Dteam_id%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22available_items%22%2C%20found%3Dlen%28available_items%29%2C%20limit%3Ditems_to_assign%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20if%20len%28available_items%29%20%3E%3D%20items_to_assign%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RequestStateError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22active_task_cap_reached%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22you%20already%20have%20%7Blen%28available_items%29%7D%20%28or%20more%29%20items%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22assigned%20to%20work%20on%3B%20finish%20some%20of%20them%20before%20requesting%20more%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%20%20%20%20%23%20items_to_assign%20is%20a%20CAP%20on%20the%20user%27s%20total%20in-progress%20items%2C%20not%0A%20%20%20%20%20%20%20%20%23%20a%20per-request%20batch%20size%3A%20top%20the%20user%20up%20to%20the%20cap.%20E.g.%20cap%205%0A%20%20%20%20%20%20%20%20%23%20with%203%20items%20still%20assigned%20-%3E%20assign%20only%202%20more.%0A%20%20%20%20%20%20%20%20items_needed%20%3D%20items_to_assign%20-%20len%28available_items%29%0A%20%20%20%20%20%20%20%20if%20maximum_items_remaining%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20items_needed%20%3D%20min%28items_needed%2C%20maximum_items_remaining%29%0A%20%20%20%20%20%20%20%20log%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Items%20needed%3A%20%7Bitems_needed%7D%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%28cap%20%7Bitems_to_assign%7D%2C%20already%20assigned%20%7Blen%28available_items%29%7D%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22maximum%20remaining%20%7Bmaximum_items_remaining%7D%29%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22items_needed%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20count%3Ditems_needed%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%3Ditems_to_assign%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20already_assigned%3Dlen%28available_items%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20maximum_remaining%3Dmaximum_items_remaining%2C%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%20%20%20%20%23%207.%20Find%20eligible%20items%3A%20not%20yet%20opened%20by%20this%20user%2C%20not%20user%0A%20%20%20%20%20%20%20%20%23%20profiles%2C%20currently%20in%20one%20of%20the%20requested%20tasks%27%20statuses%2C%20and%0A%20%20%20%20%20%20%20%20%23%20not%20already%20assigned%20to%20a%20Contributor.%20With%20select-by-category%0A%20%20%20%20%20%20%20%20%23%20enabled%2C%20also%20only%20items%20in%20the%20requested%20categories.%0A%20%20%20%20%20%20%20%20query_string%20%3D%20build_eligibility_query%28%0A%20%20%20%20%20%20%20%20%20%20%20%20user_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20statuses%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20categories%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20select_by_category%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20excluded_folder_names%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20log%28f%22Eligible-items%20query%3A%20%7Bquery_string%7D%22%29%0A%0A%20%20%20%20%20%20%20%20%23%208.%20Assign%20items_needed%20items.%20The%20explore%20index%20still%20lags%20our%20own%0A%20%20%20%20%20%20%20%20%23%20and%20the%20previous%20holder%27s%20writes%20by%20~1.2s%2C%20so%20per%20round%3A%0A%20%20%20%20%20%20%20%20%23%20%20%20a%29%20query%20a%20candidate%20pool%20and%20pick%20the%20lowest%20item%20names%20first%3B%0A%20%20%20%20%20%20%20%20%23%20%20%20b%29%20fire%20the%20assigns%3B%0A%20%20%20%20%20%20%20%20%23%20%20%20c%29%20wait%20out%20the%20index%20lag%2C%20then%20VERIFY%20each%20item%20via%0A%20%20%20%20%20%20%20%20%23%20%20%20%20%20%20get_item_by_id%20%28always%20includes%20assignments%29%20and%20only%20count%0A%20%20%20%20%20%20%20%20%23%20%20%20%20%20%20the%20ones%20that%20actually%20belong%20to%20the%20requester.%20Shortfalls%20are%0A%20%20%20%20%20%20%20%20%23%20%20%20%20%20%20covered%20by%20the%20next%20round%20%E2%80%94%20at%20most%20MAX_ASSIGN_ROUNDS%20rounds%2C%0A%20%20%20%20%20%20%20%20%23%20%20%20%20%20%20so%20a%20busy%20project%20cannot%20loop%20forever.%0A%20%20%20%20%20%20%20%20assigned_names%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20assigned_ids%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20technical_assign_failures%20%3D%200%0A%20%20%20%20%20%20%20%20technical_verification_failures%20%3D%200%0A%20%20%20%20%20%20%20%20%23%20Never%20re-assign%20an%20item%20the%20requester%20already%20holds%0A%20%20%20%20%20%20%20%20%23%20%28available_items%20can%20trail%20a%20just-finished%20request%20by%20~1s%29%2C%20and%0A%20%20%20%20%20%20%20%20%23%20never%20process%20the%20same%20candidate%20twice%20across%20rounds.%0A%20%20%20%20%20%20%20%20seen_item_ids%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20item.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20item%20in%20available_items%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28item%2C%20dict%29%20and%20item.get%28%22id%22%29%20is%20not%20None%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20for%20round_number%20in%20range%281%2C%20MAX_ASSIGN_ROUNDS%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20missing%20%3D%20items_needed%20-%20len%28assigned_names%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20missing%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20pool_limit%20%3D%20max%28CANDIDATE_POOL_SIZE%2C%20missing%29%0A%20%20%20%20%20%20%20%20%20%20%20%20eligible_items%20%3D%20query_items%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20project_id%2C%20query_string%2C%20limit%3Dpool_limit%2C%20team_id%3Dteam_id%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20candidates%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20item%20for%20item%20in%20eligible_items%20if%20item.get%28%22id%22%29%20not%20in%20seen_item_ids%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Serialized%20by%20the%20lock%3A%20hand%20out%20items%20in%20stable%20name%20order%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20because%20explore%20returns%20them%20unordered.%0A%20%20%20%20%20%20%20%20%20%20%20%20candidates.sort%28key%3Dlambda%20item%3A%20item.get%28%22name%22%29%20or%20%22%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20candidates%20%3D%20candidates%5B%3Amissing%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20log%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Assignment%20round%20%7Bround_number%7D%3A%20need%20%7Bmissing%7D%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22query%20returned%20%7Blen%28eligible_items%29%7D%2C%20picked%20%7Blen%28candidates%29%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22query%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20round%3Dround_number%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20found%3Dlen%28eligible_items%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20picked%3Dlen%28candidates%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20query%3Dquery_string%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20limit%3Dpool_limit%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20candidates%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20round_number%20%3C%20MAX_ASSIGN_ROUNDS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20log%28f%22No%20new%20candidates%3B%20sleeping%20%7BRETRY_SLEEP_SEC%7Ds%20before%20re-query%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20time.sleep%28RETRY_SLEEP_SEC%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20a%2Bb%29%20Fire%20the%20assigns%20for%20this%20round%27s%20picks.%0A%20%20%20%20%20%20%20%20%20%20%20%20pending%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20eligible%20in%20candidates%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20candidate_id%20%3D%20eligible.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20candidate_name%20%3D%20eligible.get%28%22name%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20candidate_folder%20%3D%20eligible.get%28%22folder_id%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seen_item_ids.add%28candidate_id%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20client.assign_items%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%28project_id%2C%20candidate_folder%29%2C%20%5Bcandidate_name%5D%2C%20user_id%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20technical_assign_failures%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20log%28f%22Skipping%20%7Bcandidate_name%21r%7D%3A%20assign%20failed%20%28%7Berror%7D%29%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22skip_item%22%2C%20ok%3DFalse%2C%20item%3Dcandidate_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reason%3Df%22assign%20failed%3A%20%7Berror%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pending.append%28%28candidate_id%2C%20candidate_name%2C%20candidate_folder%29%29%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20pending%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20round_number%20%3C%20MAX_ASSIGN_ROUNDS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20time.sleep%28RETRY_SLEEP_SEC%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20c%29%20Wait%20out%20the%20index%20lag%2C%20then%20keep%20only%20the%20items%20still%20ours.%0A%20%20%20%20%20%20%20%20%20%20%20%20log%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Verifying%20%7Blen%28pending%29%7D%20assign%28s%29%20after%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7BASSIGN_PROPAGATION_SLEEP_SEC%7Ds%20propagation%20wait%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20time.sleep%28ASSIGN_PROPAGATION_SLEEP_SEC%29%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20candidate_id%2C%20candidate_name%2C%20candidate_folder%20in%20pending%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fresh%20%3D%20client.get_item_by_id%28project_id%2C%20candidate_id%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20technical_verification_failures%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Unverifiable%20%E2%80%94%20treat%20as%20lost%20rather%20than%20risk%20reporting%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20an%20item%20the%20user%20does%20not%20hold.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20log%28f%22Dropping%20%7Bcandidate_name%21r%7D%3A%20verify%20read%20failed%20%28%7Berror%7D%29%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22skip_item%22%2C%20ok%3DFalse%2C%20item%3Dcandidate_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reason%3Df%22verify%20read%20failed%3A%20%7Berror%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20is_assigned_to%28fresh%2C%20user_id%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20log%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Bcandidate_name%21r%7D%3A%20lost%20assign%20race%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%28item%20belongs%20to%20another%20contributor%29%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22skip_item%22%2C%20item%3Dcandidate_name%2C%20reason%3D%22lost%20assign%20race%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Defensive%3A%20never%20report%20the%20same%20item%20twice%20in%20one%20response.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20candidate_id%20in%20assigned_ids%20or%20candidate_name%20in%20assigned_names%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20log%28f%22Skipping%20duplicate%20append%20for%20%7Bcandidate_name%21r%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20assigned_names.append%28candidate_name%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20assigned_ids.append%28candidate_id%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22assign_items%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20folder_id%3Dcandidate_folder%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20items%3D%5Bcandidate_name%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20user_id%3Duser_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20round%3Dround_number%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20finally%3A%0A%20%20%20%20%20%20%20%20release_request_lock%28ddb%2C%20lock_id%2C%20lock_owner%2C%20run%29%0A%0A%20%20%20%20if%20not%20assigned_ids%20and%20%28%0A%20%20%20%20%20%20%20%20technical_assign_failures%20or%20technical_verification_failures%0A%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22could%20not%20safely%20verify%20any%20assigned%20items%20after%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Btechnical_assign_failures%7D%20assignment%20failure%28s%29%20and%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Btechnical_verification_failures%7D%20verification-read%20failure%28s%29%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20log%28f%22Assigned%20%7Blen%28assigned_names%29%7D/%7Bitems_needed%7D%20item%28s%29%20to%20%7Buser_id%7D%22%29%0A%0A%20%20%20%20%23%209.%20Surface%20a%20bounded%20item-context%20diagnostic%20and%20the%20assignment%20report.%0A%20%20%20%20%23%20The%20caller%20only%20consumes%20assigned_items%20/%20assigned_item_ids%3B%20returning%20a%0A%20%20%20%20%23%20profile%27s%20unbounded%20requestHistory%20would%20inflate%20every%20webhook%20response.%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22item_context%22%2C%0A%20%20%20%20%20%20%20%20component_value%3Ditem_context_snapshot%28component_value%29%2C%0A%20%20%20%20%20%20%20%20metadata%3Dmetadata%2C%0A%20%20%20%20%29%0A%20%20%20%20run.output.append%28%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22item_id%22%3A%20item_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22item_name%22%3A%20item_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22user_id%22%3A%20user_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22tasks%22%3A%20tasks%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22statuses%22%3A%20statuses%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22items_to_assign%22%3A%20items_to_assign%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22items_needed%22%3A%20items_needed%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22assigned_items%22%3A%20assigned_names%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22assigned_item_ids%22%3A%20assigned_ids%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22attached_statuses%22%3A%20attached_statuses_by_task%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%29%0A",
        "config": {
          "interpreter": "3.11",
          "requirements": [
            "superannotate",
            "typing_extensions",
            "boto3"
          ]
        },
        "memory": 128,
        "time_limit": 900,
        "concurrency": 32
      }
    },
    "$team_api_key_1": {
      "type": "team_api_key",
      "label": "Team Level API Key",
      "description": "Create a 'Team-level API Key' under Security -> API Keys and mount it on both custom actions for authorization",
      "source_nodes": [
        1,
        2
      ]
    },
    "$custom_2": {
      "type": "custom_action",
      "label": "",
      "description": null,
      "source_nodes": [
        2
      ],
      "source_action_name": "smart_flow_0__user_profile_handler",
      "source_action_data": {
        "name": "smart_flow_0__user_profile_handler",
        "description": "",
        "file": "%22%22%22smart_flow_0__user_profile_handler%20%E2%80%94%20contributor%20lifecycle%20profile%20provisioning.%0A%0AStandalone%20Orchestrate%20action%3A%20this%20single%20file%20deploys%20as-is%2C%20no%20build%20step.%0ACommon%20helpers%20%28errors%2C%20envelope%2C%20explore%20search%29%20are%20inlined%20below%3B%0Acustom_actions/Shared.py%20is%20the%20reference%20they%20were%20taken%20from.%0A%0AServes%20two%20Orchestrate%20platform%20events%20with%20one%20code%20path%3A%0A%60contributor_added_to_project%60%20and%20%60contributor_project_role_updated%60.%20Both%0Acarry%20the%20user%20in%20%60context.after%60%20%28email%2C%20role_name%2C%20project_id%29%20%E2%80%94%20the%20NEW%0Arole%20decides%20eligibility.%20When%20the%20role%20is%20exactly%20%22Contributor%22%2C%20the%20handler%0Aensures%20the%20user%20has%20a%20profile%20item%20in%20the%20project%27s%20profiles%20folder%3A%0Aexisting%20profiles%20are%20adopted%20and%20repaired%2C%20missing%20ones%20are%20created%2C%0Aand%20the%20item%20always%20ends%20up%20assigned%20to%20its%20user%20with%20the%20%22Profile%22%20status.%0ASafe%20under%20duplicate%20event%20delivery.%0A%0AA%20profile%20written%20by%20this%20handler%20is%20seeded%20with%20the%20tasks%20the%20component%0Aconfig%20flags%20%60allowedByDefault%60%20AT%20THAT%20MOMENT%20%E2%80%94%20a%20creation-time%20default%2C%20not%0Aa%20live%20grant.%20After%20the%20profile%20exists%2C%20its%20%60allowedTaskIds%60%20is%20the%20single%0Asource%20of%20truth%20%28admins%20edit%20it%20directly%29%20and%20a%20later%20config%20change%20never%0Areaches%20it%3A%20only%20the%20create%20path%20and%20the%20repair%20of%20a%20completely%20missing%0Acomponent%20value%20ever%20write%20the%20seed.%0A%22%22%22%0A%0Aimport%20functools%0Aimport%20json%0Aimport%20os%0Aimport%20re%0Aimport%20sys%0Aimport%20time%0Aimport%20traceback%0Aimport%20urllib.error%0Aimport%20urllib.request%0Afrom%20enum%20import%20Enum%2C%20auto%0A%0A%23%20TypedDict%20compatibility%20for%20Python%20%3C%203.12.%20typing_extensions%20is%20installed%0A%23%20locally%20%28pydantic-validated%20SDK%20calls%20need%20the%20patch%29%20but%20missing%20in%20the%0A%23%20Orchestrate%20runtime%2C%20so%20the%20patch%20is%20best-effort%20%E2%80%94%20a%20hard%20import%20would%0A%23%20break%20the%20deployed%20action.%0Aif%20sys.version_info%20%3C%20%283%2C%2012%29%3A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20import%20typing%0A%20%20%20%20%20%20%20%20import%20typing_extensions%0A%0A%20%20%20%20%20%20%20%20typing.TypedDict%20%3D%20typing_extensions.TypedDict%0A%20%20%20%20%20%20%20%20typing.NotRequired%20%3D%20typing_extensions.NotRequired%0A%20%20%20%20%20%20%20%20typing.Required%20%3D%20typing_extensions.Required%0A%20%20%20%20except%20ModuleNotFoundError%3A%0A%20%20%20%20%20%20%20%20pass%0A%0Afrom%20superannotate%20import%20SAClient%0A%0A%23%20.env%20loading%20is%20local-development%20only%3B%20the%20Orchestrate%20runtime%20injects%20the%0A%23%20selected%20managed%20Team%20API%20key%20as%20SA_TOKEN.%0Atry%3A%0A%20%20%20%20from%20dotenv%20import%20load_dotenv%0A%0A%20%20%20%20load_dotenv%28%29%0Aexcept%20ModuleNotFoundError%3A%0A%20%20%20%20pass%0A%0AACTION_NAME%20%3D%20%22smart_flow_0__user_profile_handler%22%0ASA_TOKEN%20%3D%20os.environ.get%28%22SA_TOKEN%22%29%0AEXPLORE_SERVICE_URL%20%3D%20%22https%3A//explore-service.superannotate.com/api/v1%22%0AEXPLORE_HTTP_TIMEOUT_SEC%20%3D%2030%0A%0A%23%20---%20Cross-repo%20contract%20with%20the%20web-user-profile%20component.%20---%0A%23%20These%20identifiers%20are%20consumed/produced%20by%20the%20component%20and%20MUST%20be%20used%0A%23%20verbatim%20%E2%80%94%20never%20rename%20them%20here%20without%20a%20coordinated%20component%20change.%0A%23%0A%23%20The%20web%20component%27s%20id%20on%20the%20platform%3B%20also%20the%20prefix%20of%20the%20explore%0A%23%20query%20clause%20%28web_user_profile%28...%29%29%20and%20the%20component-value%20id%20on%20items.%0AUSER_PROFILE_COMPONENT_ID%20%3D%20%22user_profile%22%0A%23%20The%20component%27s%20config%20storage%20key%20%28matches%20CONFIG_DATA_KEY%20in%20the%0A%23%20component%27s%20ConfigData.ts%29.%0ACONFIG_DATA_KEY%20%3D%20%22user-profile-config-data%22%0A%23%20Default%20profiles-folder%20name%20when%20the%20config%20has%20neither%20a%20folder%20id%20nor%20a%0A%23%20folder%20name%20%28matches%20the%20component%27s%20config-screen%20default%29.%0ADEFAULT_PROFILE_FOLDER_NAME%20%3D%20%220_User_profiles%22%0A%0A%23%20Only%20users%20whose%20%28new%29%20project%20role%20name%20is%20exactly%20this%20get%20a%20profile.%0A%23%20Any%20other%20role%20%28Annotator%2C%20QA%2C%20ProjectAdmin%2C%20custom%20roles%2C%20...%29%20is%20a%0A%23%20recorded%20no-op.%0AELIGIBLE_ROLE_NAME%20%3D%20%22Contributor%22%0A%0A%23%20Workflow%20annotation%20status%20every%20profile%20item%20must%20end%20in.%20The%20project%0A%23%20workflow%20must%20define%20a%20status%20with%20this%20exact%20name.%0APROFILE_ANNOTATION_STATUS%20%3D%20%22Profile%22%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Errors%2C%20logging%2C%20result%20envelope%0A%23%20---------------------------------------------------------------------------%0A%0Aclass%20ActionError%28Exception%29%3A%0A%20%20%20%20%22%22%22Base%20class%20for%20expected%2C%20handled%20action%20failures.%22%22%22%0A%0A%0Aclass%20ValidationError%28ActionError%29%3A%0A%20%20%20%20%22%22%22A%20required%20value%20was%20missing%20or%20invalid%20in%20the%20event/context.%22%22%22%0A%0A%0Aclass%20ExternalServiceError%28ActionError%29%3A%0A%20%20%20%20%22%22%22An%20SDK%20call%20or%20external%20HTTP%20request%20failed.%22%22%22%0A%0A%0Aclass%20ConfigStatus%28Enum%29%3A%0A%20%20%20%20%22%22%22Published-form%20inspection%20result%20for%20the%20Smart%20Workflow%20component.%22%22%22%0A%0A%20%20%20%20NOT_PRESENT%20%3D%20auto%28%29%0A%20%20%20%20EMPTY%20%3D%20auto%28%29%0A%20%20%20%20INVALID%20%3D%20auto%28%29%0A%20%20%20%20OK%20%3D%20auto%28%29%0A%0A%0Aclass%20ComponentValueStatus%28Enum%29%3A%0A%20%20%20%20%22%22%22Whether%20an%20existing%20profile%20value%20was%20read%2C%20absent%2C%20or%20unreadable.%22%22%22%0A%0A%20%20%20%20PRESENT%20%3D%20auto%28%29%0A%20%20%20%20MISSING%20%3D%20auto%28%29%0A%20%20%20%20UNREADABLE%20%3D%20auto%28%29%0A%0A%0Adef%20log%28message%29%3A%0A%20%20%20%20print%28%22%3E%3E%3E%20INFO%20%3E%3E%3E%20%22%2C%20message%2C%20%22%5Cn%22%29%0A%0A%0Aclass%20Run%3A%0A%20%20%20%20%22%22%22Records%20handler%20operations%20and%20builds%20the%20standard%20response%20envelope.%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20action_name%29%3A%0A%20%20%20%20%20%20%20%20self.action%20%3D%20action_name%0A%20%20%20%20%20%20%20%20self.operations%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.output%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20record%28self%2C%20op%2C%20ok%3DTrue%2C%20error%3D%22%22%2C%20%2A%2Acontext%29%3A%0A%20%20%20%20%20%20%20%20entry%20%3D%20%7B%22op%22%3A%20op%2C%20%22ok%22%3A%20ok%2C%20%22error%22%3A%20error%2C%20%2A%2Acontext%7D%0A%20%20%20%20%20%20%20%20self.operations.append%28entry%29%0A%20%20%20%20%20%20%20%20return%20entry%0A%0A%20%20%20%20def%20envelope%28self%2C%20ok%2C%20error%3DNone%2C%20error_type%3DNone%29%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ok%22%3A%20ok%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22action%22%3A%20self.action%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22error%22%3A%20error%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22operations%22%3A%20self.operations%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20if%20error_type%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%5B%22error_type%22%5D%20%3D%20error_type%0A%20%20%20%20%20%20%20%20if%20self.output%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%5B%22output%22%5D%20%3D%20self.output%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%0Adef%20action%28func%29%3A%0A%20%20%20%20%22%22%22Wrap%20the%20handler%3A%20a%20successful%20run%20returns%20the%20standard%0A%20%20%20%20%60%7Bok%2C%20action%2C%20error%2C%20operations%5B%2C%20output%5D%7D%60%20envelope%3B%20a%20failed%20run%20logs%0A%20%20%20%20an%20organized%20failure%20block%20and%20RE-RAISES%20the%20error%2C%20so%20the%20Orchestrate%0A%20%20%20%20execution%20is%20marked%20failed%20instead%20of%20looking%20like%20a%20success.%20Nothing%0A%20%20%20%20consumes%20this%20action%27s%20response%20%E2%80%94%20visibility%20of%20failures%20matters%20more%0A%20%20%20%20than%20a%20structured%20error%20payload.%0A%0A%20%20%20%20Expected%20failures%20%28ActionError%20subclasses%3A%20bad%20payloads%2C%20upstream%20HTTP%0A%20%20%20%20errors%29%20are%20logged%20without%20a%20traceback%3B%20anything%20else%20is%20a%20bug%20and%20keeps%0A%20%20%20%20the%20full%20traceback.%20The%20wrapped%20handler%20signature%20is%0A%20%20%20%20%60handler%28event%2C%20context%2C%20run%29%60%3B%20the%20SA%20runtime%20still%20calls%20it%20as%0A%20%20%20%20%60handler%28event%2C%20context%29%60.%22%22%22%0A%0A%20%20%20%20%40functools.wraps%28func%29%0A%20%20%20%20def%20wrapper%28event%2C%20context%29%3A%0A%20%20%20%20%20%20%20%20run%20%3D%20Run%28ACTION_NAME%29%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20func%28event%2C%20context%2C%20run%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20run.envelope%28ok%3DTrue%29%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20expected%20%3D%20isinstance%28error%2C%20ActionError%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28%22%3D%22%20%2A%2060%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20ACTION%20FAILED%20%28%7B%27handled%27%20if%20expected%20else%20%27UNEXPECTED%27%7D%29%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20%20%20action%20%3A%20%7Brun.action%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20%20%20type%20%20%20%3A%20%7Btype%28error%29.__name__%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28f%22%20%20%20error%20%20%3A%20%7Berror%7D%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20expected%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20traceback.print_exc%28%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28%22%3D%22%20%2A%2060%29%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%0A%0A%20%20%20%20return%20wrapper%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20SuperAnnotate%20access%0A%23%20---------------------------------------------------------------------------%0A%0Adef%20get_sa_client%28%29%3A%0A%20%20%20%20if%20not%20isinstance%28SA_TOKEN%2C%20str%29%20or%20not%20SA_TOKEN.strip%28%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%22SA_TOKEN%20is%20missing%20or%20blank%22%29%0A%20%20%20%20return%20SAClient%28SA_TOKEN%29%0A%0A%0Adef%20get_team_id%28client%29%3A%0A%20%20%20%20%22%22%22Read%20the%20team%20id%20once%20from%20the%20managed%20token%27s%20SDK-authenticated%20team.%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20team_metadata%20%3D%20client.get_team_metadata%28%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28f%22could%20not%20read%20team%20metadata%3A%20%7Berror%7D%22%29%20from%20error%0A%20%20%20%20team_id%20%3D%20team_metadata.get%28%22id%22%29%20if%20isinstance%28team_metadata%2C%20dict%29%20else%20None%0A%20%20%20%20if%20isinstance%28team_id%2C%20bool%29%20or%20not%20isinstance%28team_id%2C%20int%29%20or%20team_id%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22team%20metadata%20had%20no%20positive%20integer%20id%3A%20%7Bteam_metadata%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20return%20team_id%0A%0A%0Adef%20query_items%28project_id%2C%20query%2C%20limit%2C%20team_id%29%3A%0A%20%20%20%20%22%22%22SAQuL%20item%20search%20straight%20against%20the%20explore%20service%2C%20with%20a%20hard%0A%20%20%20%20limit.%20Unlike%20SAClient.query%28%29%20%E2%80%94%20which%20pages%20through%20every%20matching%20item%0A%20%20%20%20with%20no%20way%20to%20stop%20early%20%28~7%20minutes%20on%2010k%20items%29%20%E2%80%94%20this%20asks%20the%0A%20%20%20%20backend%20for%20at%20most%20%60limit%60%20items%20in%20a%20single%20request.%20Returns%20a%20list%20of%0A%20%20%20%20item%20dicts%20%28includes%20name%20and%20folder_id%29.%22%22%22%0A%20%20%20%20url%20%3D%20%28%0A%20%20%20%20%20%20%20%20f%22%7BEXPLORE_SERVICE_URL%7D/items/search%22%0A%20%20%20%20%20%20%20%20f%22%3Fproject_id%3D%7Bproject_id%7D%26team_id%3D%7Bteam_id%7D%22%0A%20%20%20%20%20%20%20%20%22%26includeFolderNames%3Dtrue%26includeInstances%3Dfalse%26parseQuery%3Dtrue%22%0A%20%20%20%20%29%0A%20%20%20%20body%20%3D%20json.dumps%28%7B%22image_index%22%3A%200%2C%20%22limit%22%3A%20limit%2C%20%22query%22%3A%20query%7D%29%0A%20%20%20%20request%20%3D%20urllib.request.Request%28%0A%20%20%20%20%20%20%20%20url%2C%0A%20%20%20%20%20%20%20%20data%3Dbody.encode%28%22utf-8%22%29%2C%0A%20%20%20%20%20%20%20%20method%3D%22POST%22%2C%0A%20%20%20%20%20%20%20%20headers%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Authorization%22%3A%20SA_TOKEN%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22authType%22%3A%20%22api_key%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Content-Type%22%3A%20%22application/json%22%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%29%0A%20%20%20%20log%28f%22POST%20%7Burl%7D%22%29%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20with%20urllib.request.urlopen%28%0A%20%20%20%20%20%20%20%20%20%20%20%20request%2C%20timeout%3DEXPLORE_HTTP_TIMEOUT_SEC%0A%20%20%20%20%20%20%20%20%29%20as%20response%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raw_body%20%3D%20response.read%28%29.decode%28%22utf-8%22%29%0A%20%20%20%20except%20urllib.error.HTTPError%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22explore%20search%20failed%20with%20status%20%7Berror.code%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Berror.read%28%29.decode%28%27utf-8%27%29%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20except%20TimeoutError%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22explore%20search%20timed%20out%20after%20%7BEXPLORE_HTTP_TIMEOUT_SEC%7Ds%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20except%20urllib.error.URLError%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28f%22explore%20search%20failed%3A%20%7Berror.reason%7D%22%29%20from%20error%0A%0A%20%20%20%20items%20%3D%20json.loads%28raw_body%29%20if%20raw_body%20else%20None%0A%20%20%20%20if%20not%20isinstance%28items%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22unexpected%20explore%20search%20response%20for%20project%20%7Bproject_id%7D%3A%20%7Bitems%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20return%20items%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Profile%20helpers%0A%23%20---------------------------------------------------------------------------%0A%0Adef%20new_profile_component_value%28default_task_ids%29%3A%0A%20%20%20%20%22%22%22The%20exact%20component%20value%20written%20on%20a%20NEWLY%20created%20profile%20item.%0A%0A%20%20%20%20%60allowedTaskIds%60%20is%20seeded%20with%20the%20config%27s%20default%20tasks%20as%20they%20are%20at%0A%20%20%20%20creation%20time%20and%20is%20the%20profile%27s%20own%20state%20from%20then%20on%20%E2%80%94%20nothing%20in%20the%0A%20%20%20%20pipeline%20re-applies%20the%20config%20to%20an%20existing%20profile.%0A%0A%20%20%20%20Contract%20shape%20shared%20with%20the%20component%20%E2%80%94%20%60userProfile%60%20is%20a%20STRING%0A%20%20%20%20%28%22true%22%29%2C%20not%20a%20boolean%3A%20the%20Explore%20key%20schema%20on%20the%20component%20side%0A%20%20%20%20requires%20it.%20Never%20write%20the%20legacy%20%60isUserProfile%60%20boolean.%22%22%22%0A%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%22userProfile%22%3A%20%22true%22%2C%0A%20%20%20%20%20%20%20%20%22allowedTaskIds%22%3A%20list%28default_task_ids%29%2C%0A%20%20%20%20%20%20%20%20%22pauseUser%22%3A%20False%2C%0A%20%20%20%20%20%20%20%20%22requestedTaskIds%22%3A%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20%22users%22%3A%20%5B%5D%2C%0A%20%20%20%20%7D%0A%0A%0Adef%20resolve_default_task_ids%28config_data%2C%20run%29%3A%0A%20%20%20%20%22%22%22Task%20ids%20to%20seed%20into%20a%20new%20profile%27s%20%60allowedTaskIds%60%3A%20every%20config%0A%20%20%20%20task%20flagged%20%60allowedByDefault%60%2C%20in%20config%20order%2C%20de-duplicated.%0A%0A%20%20%20%20%60allowedByDefault%60%20must%20be%20exactly%20True%20%E2%80%94%20the%20key%20is%20optional%2C%20older%20saved%0A%20%20%20%20configs%20omit%20it%2C%20and%20anything%20truthy-but-not-True%20is%20not%20a%20flag.%0A%0A%20%20%20%20A%20missing%20or%20malformed%20task%20list%20degrades%20to%20%22no%20defaults%22%20instead%20of%0A%20%20%20%20failing%3A%20a%20contributor%20must%20still%20get%20a%20provisioned%20profile%20even%20when%20the%0A%20%20%20%20config%20is%20half-written.%20The%20degradation%20is%20recorded%20in%20the%20run%20trace%20so%20it%0A%20%20%20%20is%20visible%20in%20the%20Orchestrate%20execution.%22%22%22%0A%20%20%20%20tasks%20%3D%20config_data.get%28%22tasks%22%29%0A%20%20%20%20if%20not%20isinstance%28tasks%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22default_tasks_seeded%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20task_ids%3D%5B%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20degraded%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reason%3Df%22config%20task%20list%20is%20%7Btype%28tasks%29.__name__%7D%2C%20expected%20a%20list%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%20%5B%5D%0A%0A%20%20%20%20task_ids%20%3D%20%5B%5D%0A%20%20%20%20unusable%20%3D%200%0A%20%20%20%20for%20entry%20in%20tasks%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28entry%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20unusable%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20task_id%20%3D%20entry.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28task_id%2C%20str%29%20or%20not%20task_id.strip%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20unusable%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20if%20entry.get%28%22allowedByDefault%22%29%20is%20not%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20if%20task_id%20not%20in%20task_ids%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20task_ids.append%28task_id%29%0A%0A%20%20%20%20record%20%3D%20%7B%22task_ids%22%3A%20task_ids%2C%20%22degraded%22%3A%20unusable%20%3E%200%7D%0A%20%20%20%20if%20unusable%3A%0A%20%20%20%20%20%20%20%20record%5B%22reason%22%5D%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bunusable%7D%20of%20%7Blen%28tasks%29%7D%20config%20task%20entries%20are%20unusable%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%28not%20an%20object%2C%20or%20no%20task%20id%29%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20run.record%28%22default_tasks_seeded%22%2C%20%2A%2Arecord%29%0A%20%20%20%20return%20task_ids%0A%0A%0Adef%20normalize_email%28email%29%3A%0A%20%20%20%20%22%22%22Profile%20item%20name%3A%20the%20user%27s%20email%20normalized%20to%20alphanumeric%20form%20%E2%80%94%0A%20%20%20%20lowercase%2C%20%60%40%60%20replaced%20by%20the%20literal%20%60at%60%2C%20every%20other%20non-alphanumeric%0A%20%20%20%20character%20dropped%20%28Example%40Mail.com%20-%3E%20exampleatmailcom%29.%22%22%22%0A%20%20%20%20return%20re.sub%28r%22%5B%5Ea-z0-9%5D%22%2C%20%22%22%2C%20email.strip%28%29.lower%28%29.replace%28%22%40%22%2C%20%22at%22%29%29%0A%0A%0Adef%20parse_positive_integer%28value%2C%20field%29%3A%0A%20%20%20%20%22%22%22Accept%20only%20positive%20whole%20numeric%20IDs%3B%20never%20truncate%20a%20float.%22%22%22%0A%20%20%20%20if%20isinstance%28value%2C%20bool%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20if%20isinstance%28value%2C%20int%29%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20value%0A%20%20%20%20elif%20isinstance%28value%2C%20float%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20value.is_integer%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20%20%20%20%20result%20%3D%20int%28value%29%0A%20%20%20%20elif%20isinstance%28value%2C%20str%29%3A%0A%20%20%20%20%20%20%20%20text%20%3D%20value.strip%28%29%0A%20%20%20%20%20%20%20%20if%20not%20re.fullmatch%28r%22%5B1-9%5D%5B0-9%5D%2A%22%2C%20text%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20%20%20%20%20result%20%3D%20int%28text%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20if%20result%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28f%22%7Bfield%7D%20must%20be%20a%20positive%20whole%20number%22%29%0A%20%20%20%20return%20result%0A%0A%0Adef%20get_assignment_emails%28item%29%3A%0A%20%20%20%20%22%22%22Distinct%20normalized%20assignment%20emails%20on%20an%20item.%22%22%22%0A%20%20%20%20assignments%20%3D%20item.get%28%22assignments%22%29%20if%20isinstance%28item%2C%20dict%29%20else%20None%0A%20%20%20%20if%20not%20isinstance%28assignments%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20return%20%5B%5D%0A%20%20%20%20emails%20%3D%20%5B%5D%0A%20%20%20%20for%20assignment%20in%20assignments%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28assignment%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20for%20key%20in%20%28%22user_id%22%2C%20%22user_email%22%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20value%20%3D%20assignment.get%28key%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28value%2C%20str%29%20and%20value.strip%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20normalized%20%3D%20value.strip%28%29.lower%28%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20normalized%20not%20in%20emails%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20emails.append%28normalized%29%0A%20%20%20%20return%20emails%0A%0A%0Adef%20get_live_assignment_emails%28item%2C%20item_id%29%3A%0A%20%20%20%20%22%22%22Read%20live%20ownership%20data%20strictly%3B%20unknown%20ownership%20is%20never%20unassigned.%22%22%22%0A%20%20%20%20if%20not%20isinstance%28item%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20inspect%20live%20assignments%20for%20profile%20item%20%7Bitem_id%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22expected%20an%20item%20object%2C%20got%20%7Btype%28item%29.__name__%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20if%20%22assignments%22%20not%20in%20item%20or%20not%20isinstance%28item%5B%22assignments%22%5D%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20inspect%20live%20assignments%20for%20profile%20item%20%7Bitem_id%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22expected%20an%20assignments%20list%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20emails%20%3D%20%5B%5D%0A%20%20%20%20for%20index%2C%20assignment%20in%20enumerate%28item%5B%22assignments%22%5D%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28assignment%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20inspect%20live%20assignments%20for%20profile%20item%20%7Bitem_id%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22assignment%20%7Bindex%7D%20is%20not%20an%20object%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20assignment_emails%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20key%20in%20%28%22user_id%22%2C%20%22user_email%22%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20value%20%3D%20assignment.get%28key%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28value%2C%20str%29%20and%20value.strip%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20assignment_emails.append%28value.strip%28%29.lower%28%29%29%0A%20%20%20%20%20%20%20%20if%20not%20assignment_emails%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20inspect%20live%20assignments%20for%20profile%20item%20%7Bitem_id%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22assignment%20%7Bindex%7D%20has%20no%20usable%20user%20id%20or%20email%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20for%20email%20in%20assignment_emails%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20email%20not%20in%20emails%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20emails.append%28email%29%0A%20%20%20%20return%20emails%0A%0A%0Adef%20select_profile_adoption_item%28items%2C%20item_name%2C%20email%29%3A%0A%20%20%20%20%22%22%22Choose%20a%20same-name%20item%20without%20ever%20reassigning%20another%20user%27s%20profile.%22%22%22%0A%20%20%20%20if%20not%20isinstance%28items%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20inspect%20existing%20profile%20item%20%7Bitem_name%21r%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22expected%20a%20list%2C%20got%20%7Btype%28items%29.__name__%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20requested_email%20%3D%20email.strip%28%29.lower%28%29%0A%20%20%20%20unassigned_item%20%3D%20None%0A%20%20%20%20conflicting_emails%20%3D%20%5B%5D%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28item%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20assigned_emails%20%3D%20get_assignment_emails%28item%29%0A%20%20%20%20%20%20%20%20if%20requested_email%20in%20assigned_emails%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20item%0A%20%20%20%20%20%20%20%20if%20not%20assigned_emails%20and%20unassigned_item%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20unassigned_item%20%3D%20item%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20conflicting_emails.extend%28assigned_emails%29%0A%0A%20%20%20%20if%20unassigned_item%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20return%20unassigned_item%0A%20%20%20%20if%20conflicting_emails%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22profile%20name%20conflict%20for%20%7Bitem_name%21r%7D%3A%20the%20existing%20profile%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22item%20is%20assigned%20to%20another%20contributor%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20f%22could%20not%20identify%20a%20usable%20existing%20profile%20item%20%7Bitem_name%21r%7D%22%0A%20%20%20%20%29%0A%0A%0Adef%20find_user_profile_component%28node%29%3A%0A%20%20%20%20%22%22%22Return%20the%20genuine%20Smart%20Workflow%20web-component%20node%2C%20if%20present.%0A%0A%20%20%20%20Form%20templates%20can%20nest%20components%20in%20containers%2C%20sections%2C%20or%20lists%2C%20so%0A%20%20%20%20traverse%20every%20JSON%20object%20and%20list.%20An%20element%20merely%20named%0A%20%20%20%20%60%60user_profile%60%60%20is%20not%20sufficient%3A%20its%20type%20must%20be%20exactly%0A%20%20%20%20%60%60webComponent%60%60.%0A%20%20%20%20%22%22%22%0A%20%20%20%20if%20isinstance%28node%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20if%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20node.get%28%22id%22%29%20%3D%3D%20USER_PROFILE_COMPONENT_ID%0A%20%20%20%20%20%20%20%20%20%20%20%20and%20node.get%28%22type%22%29%20%3D%3D%20%22webComponent%22%0A%20%20%20%20%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20node%0A%20%20%20%20%20%20%20%20for%20value%20in%20node.values%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20match%20%3D%20find_user_profile_component%28value%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20match%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20match%0A%20%20%20%20elif%20isinstance%28node%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20for%20value%20in%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20match%20%3D%20find_user_profile_component%28value%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20match%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20match%0A%20%20%20%20return%20None%0A%0A%0Adef%20inspect_published_component_context%28client%2C%20project_id%29%3A%0A%20%20%20%20%22%22%22Classify%20the%20Smart%20Workflow%20component%20in%20the%20published%20form%20template.%0A%0A%20%20%20%20%60%60get_component_config%60%60%20cannot%20distinguish%20a%20missing%20component%20from%20a%0A%20%20%20%20matching%20component%20with%20blank%20context.%20The%20editor%20template%20preserves%20that%0A%20%20%20%20distinction%2C%20including%20for%20non-Multimodal%20projects%20that%20return%20an%20empty%0A%20%20%20%20template.%0A%20%20%20%20%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20template%20%3D%20client.controller.projects.get_editor_template%28project_id%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20published%20editor%20template%20for%20project%20%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%0A%20%20%20%20component%20%3D%20find_user_profile_component%28template%29%0A%20%20%20%20if%20component%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20ConfigStatus.NOT_PRESENT%2C%20None%0A%0A%20%20%20%20context%20%3D%20component.get%28%22context%22%29%0A%20%20%20%20if%20context%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20ConfigStatus.EMPTY%2C%20None%0A%20%20%20%20if%20isinstance%28context%2C%20str%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20context.strip%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20ConfigStatus.EMPTY%2C%20None%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20context%20%3D%20json.loads%28context%29%0A%20%20%20%20%20%20%20%20except%20%28TypeError%2C%20ValueError%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20ConfigStatus.INVALID%2C%20None%0A%20%20%20%20elif%20not%20isinstance%28context%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20return%20ConfigStatus.INVALID%2C%20None%0A%0A%20%20%20%20if%20not%20isinstance%28context%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20return%20ConfigStatus.INVALID%2C%20None%0A%20%20%20%20return%20ConfigStatus.OK%2C%20context%0A%0A%0Adef%20read_config_data%28client%2C%20project_id%2C%20run%29%3A%0A%20%20%20%20%22%22%22Read%20Smart%20Workflow%20config%20from%20its%20published%20form%20component.%0A%0A%20%20%20%20A%20project%20with%20no%20genuine%20component%20is%20intentionally%20ignored.%20A%20genuine%0A%20%20%20%20component%20with%20an%20unusable%20context%20is%20a%20configuration%20error%20so%20an%20admin%0A%20%20%20%20can%20correct%20and%20publish%20the%20form%20instead%20of%20silently%20skipping%20profiles.%0A%20%20%20%20%22%22%22%0A%20%20%20%20status%2C%20context%20%3D%20inspect_published_component_context%28client%2C%20project_id%29%0A%20%20%20%20if%20status%20is%20ConfigStatus.NOT_PRESENT%3A%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22skip_project_not_configured%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20project_id%3Dproject_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20component_id%3DUSER_PROFILE_COMPONENT_ID%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reason%3Df%22no%20genuine%20%7BUSER_PROFILE_COMPONENT_ID%7D%20web%20component%20in%20published%20form%20template%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20if%20status%20is%20ConfigStatus.EMPTY%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22project%20%7Bproject_id%7D%20has%20a%20genuine%20%7BUSER_PROFILE_COMPONENT_ID%21r%7D%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22web%20component%2C%20but%20its%20Smart%20Workflow%20config%20context%20is%20blank%3B%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22configure%20it%20and%20publish%20the%20form%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20if%20status%20is%20ConfigStatus.INVALID%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22project%20%7Bproject_id%7D%20has%20a%20genuine%20%7BUSER_PROFILE_COMPONENT_ID%21r%7D%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22web%20component%20with%20invalid%20Smart%20Workflow%20config%20context%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20config_data%20%3D%20context.get%28CONFIG_DATA_KEY%29%0A%20%20%20%20if%20not%20isinstance%28config_data%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22published%20component%20context%20for%20project%20%7Bproject_id%7D%20has%20no%20valid%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22dictionary%20%7BCONFIG_DATA_KEY%21r%7D%20payload%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22config_read%22%2C%0A%20%20%20%20%20%20%20%20folder_id_in_config%3Dconfig_data.get%28%22userProfileFolderId%22%29%2C%0A%20%20%20%20%20%20%20%20folder_name_in_config%3Dconfig_data.get%28%22userProfileFolderName%22%29%2C%0A%20%20%20%20%29%0A%20%20%20%20return%20config_data%0A%0A%0Adef%20resolve_profiles_folder%28client%2C%20project_id%2C%20config_data%2C%20run%29%3A%0A%20%20%20%20%22%22%22Resolve%20a%20live%20profiles%20folder%20without%20changing%20published%20config.%0A%0A%20%20%20%20A%20positive%20configured%20id%20is%20only%20usable%20while%20it%20remains%20in%20the%20project%27s%0A%20%20%20%20folder%20list.%20A%20stale%20id%20falls%20back%20to%20the%20configured%20recovery%20name%20%28or%0A%20%20%20%20the%20default%29%2C%20then%20reuses%20or%20creates%20that%20folder.%20Listing%20failures%20are%0A%20%20%20%20external-service%20failures%3B%20a%20later%20item-list%20authorization%20error%20must%20not%0A%20%20%20%20be%20mistaken%20for%20evidence%20that%20the%20configured%20folder%20was%20deleted.%0A%20%20%20%20%22%22%22%0A%20%20%20%20raw_folder_id%20%3D%20config_data.get%28%22userProfileFolderId%22%29%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20folder_id%20%3D%20parse_positive_integer%28%0A%20%20%20%20%20%20%20%20%20%20%20%20raw_folder_id%2C%20%22config%20userProfileFolderId%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20except%20ValidationError%3A%0A%20%20%20%20%20%20%20%20folder_id%20%3D%20None%0A%0A%20%20%20%20folder_name%20%3D%20config_data.get%28%22userProfileFolderName%22%29%0A%20%20%20%20if%20not%20isinstance%28folder_name%2C%20str%29%20or%20not%20folder_name.strip%28%29%3A%0A%20%20%20%20%20%20%20%20folder_name%20%3D%20DEFAULT_PROFILE_FOLDER_NAME%0A%20%20%20%20folder_name%20%3D%20folder_name.strip%28%29%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20folders%20%3D%20client.list_folders%28project_id%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20list%20folders%20for%20project%20%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20if%20not%20isinstance%28folders%2C%20list%29%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20list%20folders%20for%20project%20%7Bproject_id%7D%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22unexpected%20response%20%7Bfolders%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20if%20folder_id%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20for%20folder%20in%20folders%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance%28folder%2C%20dict%29%20and%20folder.get%28%22id%22%29%20%3D%3D%20folder_id%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22resolve_folder%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20folder_id%3Dfolder_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20source%3D%22config_id%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20folder_id%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22stale_config_folder_id%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20folder_id%3Dfolder_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20fallback_folder_name%3Dfolder_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reason%3D%22not_found_in_project_folders%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20for%20folder%20in%20folders%3A%0A%20%20%20%20%20%20%20%20if%20isinstance%28folder%2C%20dict%29%20and%20folder.get%28%22name%22%29%20%3D%3D%20folder_name%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20folder_id%20%3D%20folder.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22resolve_folder%22%2C%20folder_id%3Dfolder_id%2C%20folder_name%3Dfolder_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20source%3D%22name_lookup%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20folder_id%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20created%20%3D%20client.create_folder%28project_id%2C%20folder_name%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20create%20profiles%20folder%20%7Bfolder_name%21r%7D%20in%20project%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20folder_id%20%3D%20created.get%28%22id%22%29%20if%20isinstance%28created%2C%20dict%29%20else%20None%0A%20%20%20%20if%20folder_id%20is%20None%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22created%20profiles%20folder%20%7Bfolder_name%21r%7D%20but%20no%20id%20came%20back%3A%20%7Bcreated%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22resolve_folder%22%2C%20folder_id%3Dfolder_id%2C%20folder_name%3Dfolder_name%2C%0A%20%20%20%20%20%20%20%20source%3D%22created%22%2C%0A%20%20%20%20%29%0A%20%20%20%20return%20folder_id%0A%0A%0Adef%20read_component_value%28client%2C%20project_id%2C%20folder_id%2C%20item_id%29%3A%0A%20%20%20%20%22%22%22Classify%20an%20existing%20profile%20value%20without%20conflating%20absence%20and%20failure.%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20with%20client.item_context%28%28project_id%2C%20folder_id%29%2C%20item_id%29%20as%20item_ctx%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20value%20%3D%20item_ctx.get_component_value%28USER_PROFILE_COMPONENT_ID%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20return%20ComponentValueStatus.UNREADABLE%2C%20error%0A%20%20%20%20if%20value%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20ComponentValueStatus.MISSING%2C%20None%0A%20%20%20%20return%20ComponentValueStatus.PRESENT%2C%20value%0A%0A%0Adef%20set_component_value%28client%2C%20project_id%2C%20folder_id%2C%20item_id%2C%20default_task_ids%29%3A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20with%20client.item_context%28%28project_id%2C%20folder_id%29%2C%20item_id%29%20as%20item_ctx%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20item_ctx.set_component_value%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20USER_PROFILE_COMPONENT_ID%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20new_profile_component_value%28default_task_ids%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20set%20the%20%7BUSER_PROFILE_COMPONENT_ID%7D%20component%20value%20on%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22item%20%7Bitem_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%0A%0Adef%20initialize_or_preserve_profile_value%28%0A%20%20%20%20client%2C%20project_id%2C%20folder_id%2C%20item_id%2C%20item_name%2C%20default_task_ids%2C%20created%2C%20run%0A%29%3A%0A%20%20%20%20%22%22%22Initialize%20a%20new/missing%20value%20while%20preserving%20a%20readable%20existing%20one.%0A%0A%20%20%20%20An%20unreadable%20value%20is%20deliberately%20left%20alone%3A%20status%20and%20assignment%20can%0A%20%20%20%20still%20be%20repaired%2C%20but%20replacing%20its%20data%20would%20discard%20user-managed%20task%0A%20%20%20%20grants%2C%20pause%20state%2C%20or%20request%20history.%0A%20%20%20%20%22%22%22%0A%20%20%20%20if%20created%3A%0A%20%20%20%20%20%20%20%20set_component_value%28client%2C%20project_id%2C%20folder_id%2C%20item_id%2C%20default_task_ids%29%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22set_component_value%22%2C%20item%3Ditem_name%2C%20seeded_task_ids%3Ddefault_task_ids%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20component_status%2C%20component_value%20%3D%20read_component_value%28%0A%20%20%20%20%20%20%20%20client%2C%20project_id%2C%20folder_id%2C%20item_id%0A%20%20%20%20%29%0A%20%20%20%20if%20component_status%20is%20ComponentValueStatus.MISSING%3A%0A%20%20%20%20%20%20%20%20set_component_value%28client%2C%20project_id%2C%20folder_id%2C%20item_id%2C%20default_task_ids%29%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22set_component_value%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item%3Ditem_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20repaired%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20seeded_task_ids%3Ddefault_task_ids%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20if%20component_status%20is%20ComponentValueStatus.UNREADABLE%3A%0A%20%20%20%20%20%20%20%20degraded_reason%20%3D%20%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20the%20existing%20profile%20component%20value%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bcomponent_value%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%22component_value_unreadable%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ok%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item%3Ditem_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_id%3Ditem_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20error%3Ddegraded_reason%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%20degraded_reason%0A%20%20%20%20return%20None%0A%0A%0Adef%20resolve_role_name%28after%29%3A%0A%20%20%20%20%22%22%22Role%20string%20from%20the%20contributor%20event%20payload.%20Orchestrate%20docs%20show%0A%20%20%20%20%60role_name%60%3B%20some%20live%20event%20deliveries%20use%20%60role%60%20instead.%20Missing%20both%0A%20%20%20%20is%20treated%20as%20unknown%20%28ineligible%29.%22%22%22%0A%20%20%20%20for%20key%20in%20%28%22role_name%22%2C%20%22role%22%29%3A%0A%20%20%20%20%20%20%20%20value%20%3D%20after.get%28key%29%0A%20%20%20%20%20%20%20%20if%20isinstance%28value%2C%20str%29%20and%20value.strip%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20value.strip%28%29%0A%20%20%20%20return%20None%0A%0A%0Adef%20assign_item%28client%2C%20project_id%2C%20folder_id%2C%20item_name%2C%20email%2C%20run%29%3A%0A%20%20%20%20%22%22%22Assign%20the%20profile%20item%20to%20its%20user.%20Contributor-added%20events%20can%20fire%0A%20%20%20%20before%20the%20project%20membership%20is%20fully%20readable%20by%20assign_items%2C%20so%20retry%0A%20%20%20%20briefly%20before%20failing.%22%22%22%0A%20%20%20%20last_error%20%3D%20None%0A%20%20%20%20for%20attempt%20in%20range%281%2C%204%29%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20client.assign_items%28%28project_id%2C%20folder_id%29%2C%20%5Bitem_name%5D%2C%20email%29%0A%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22assign_item%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20item%3Ditem_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20user%3Demail%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20folder_id%3Dfolder_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20attempt%3Dattempt%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20last_error%20%3D%20error%0A%20%20%20%20%20%20%20%20%20%20%20%20log%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22assign_items%20attempt%20%7Battempt%7D/3%20failed%20for%20%7Bitem_name%21r%7D%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22-%3E%20%7Bemail%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20attempt%20%3C%203%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20time.sleep%281.5%20%2A%20attempt%29%0A%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20f%22could%20not%20assign%20profile%20item%20%7Bitem_name%21r%7D%20to%20%7Bemail%7D%3A%20%7Blast_error%7D%22%0A%20%20%20%20%29%20from%20last_error%0A%0A%0Adef%20is_profile_owned_by_or_unassigned%28%0A%20%20%20%20client%2C%20project_id%2C%20item_id%2C%20item_name%2C%20email%0A%29%3A%0A%20%20%20%20%22%22%22Return%20whether%20the%20profile%20is%20already%20owned%20by%20the%20requested%20user.%0A%0A%20%20%20%20%60%60list_items%60%60%20and%20Explore%20can%20be%20stale%20or%20omit%20assignment%20details%2C%20so%0A%20%20%20%20this%20is%20the%20authoritative%20no-steal%20check.%20It%20must%20run%20before%20component%20or%0A%20%20%20%20status%20writes%20to%20avoid%20altering%20a%20profile%20that%20belongs%20to%20another%0A%20%20%20%20contributor.%0A%20%20%20%20%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20fresh%20%3D%20client.get_item_by_id%28project_id%2C%20item_id%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20re-read%20profile%20item%20%7Bitem_id%7D%20before%20provisioning%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20assigned_emails%20%3D%20get_live_assignment_emails%28fresh%2C%20item_id%29%0A%20%20%20%20if%20email.strip%28%29.lower%28%29%20in%20assigned_emails%3A%0A%20%20%20%20%20%20%20%20return%20True%0A%20%20%20%20if%20assigned_emails%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22profile%20name%20conflict%20for%20%7Bitem_name%21r%7D%3A%20the%20existing%20profile%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22item%20is%20assigned%20to%20another%20contributor%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20return%20False%0A%0A%0Adef%20set_profile_status%28client%2C%20project_id%2C%20folder_id%2C%20item_name%2C%20run%29%3A%0A%20%20%20%20%22%22%22Set%20the%20item%27s%20workflow%20annotation%20status%20to%20PROFILE_ANNOTATION_STATUS.%22%22%22%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20client.set_annotation_statuses%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%28project_id%2C%20folder_id%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20PROFILE_ANNOTATION_STATUS%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bitem_name%5D%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20set%20annotation%20status%20%7BPROFILE_ANNOTATION_STATUS%21r%7D%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22on%20profile%20item%20%7Bitem_name%21r%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22set_annotation_status%22%2C%0A%20%20%20%20%20%20%20%20item%3Ditem_name%2C%0A%20%20%20%20%20%20%20%20status%3DPROFILE_ANNOTATION_STATUS%2C%0A%20%20%20%20%20%20%20%20folder_id%3Dfolder_id%2C%0A%20%20%20%20%29%0A%0A%0A%23%20---------------------------------------------------------------------------%0A%23%20Handler%0A%23%20---------------------------------------------------------------------------%0A%0A%40action%0Adef%20handler%28event%2C%20context%2C%20run%29%3A%0A%20%20%20%20op%20%3D%20context.get%28%22op%22%29%20if%20isinstance%28context%2C%20dict%29%20else%20None%0A%20%20%20%20event_name%20%3D%20event.get%28%22event%22%29%20if%20isinstance%28event%2C%20dict%29%20else%20None%0A%0A%20%20%20%20after%20%3D%20context.get%28%22after%22%29%20if%20isinstance%28context%2C%20dict%29%20else%20None%0A%20%20%20%20if%20not%20isinstance%28after%2C%20dict%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%22context.after%20is%20required%20and%20must%20be%20an%20object%22%29%0A%0A%20%20%20%20email%20%3D%20after.get%28%22email%22%29%0A%20%20%20%20if%20not%20isinstance%28email%2C%20str%29%20or%20not%20email.strip%28%29%3A%0A%20%20%20%20%20%20%20%20raise%20ValidationError%28%22after.email%20is%20required%20and%20must%20be%20a%20non-empty%20string%22%29%0A%20%20%20%20email%20%3D%20email.strip%28%29%0A%0A%20%20%20%20project_id%20%3D%20parse_positive_integer%28after.get%28%22project_id%22%29%2C%20%22after.project_id%22%29%0A%0A%20%20%20%20role_name%20%3D%20resolve_role_name%28after%29%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22validate%22%2C%0A%20%20%20%20%20%20%20%20event%3Devent_name%2C%0A%20%20%20%20%20%20%20%20context_op%3Dop%2C%0A%20%20%20%20%20%20%20%20email%3Demail%2C%0A%20%20%20%20%20%20%20%20role_name%3Drole_name%2C%0A%20%20%20%20%20%20%20%20project_id%3Dproject_id%2C%0A%20%20%20%20%20%20%20%20role_keys%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22role_name%22%3A%20after.get%28%22role_name%22%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22role%22%3A%20after.get%28%22role%22%29%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%29%0A%0A%20%20%20%20def%20report%28%0A%20%20%20%20%20%20%20%20eligible%2C%20created%2C%20item_id%3DNone%2C%20item_name%3DNone%2C%20folder_id%3DNone%2C%0A%20%20%20%20%20%20%20%20skip_reason%3DNone%2C%20degraded%3DFalse%2C%20degraded_reason%3DNone%2C%0A%20%20%20%20%29%3A%0A%20%20%20%20%20%20%20%20row%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22event%22%3A%20event_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22op%22%3A%20op%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22user%22%3A%20email%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22role_name%22%3A%20role_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22eligible%22%3A%20eligible%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22profile_created%22%3A%20created%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22profile_existed%22%3A%20eligible%20and%20not%20created%20if%20skip_reason%20is%20None%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22item_id%22%3A%20item_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22item_name%22%3A%20item_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22folder_id%22%3A%20folder_id%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20if%20skip_reason%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row%5B%22skip_reason%22%5D%20%3D%20skip_reason%0A%20%20%20%20%20%20%20%20if%20degraded%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row%5B%22degraded%22%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20row%5B%22degraded_reason%22%5D%20%3D%20degraded_reason%0A%20%20%20%20%20%20%20%20run.output.append%28row%29%0A%0A%20%20%20%20def%20finalize_profile%28item_id%2C%20item_name%2C%20profile_folder_id%2C%20created%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22Safely%20make%20a%20selected%20profile%20usable%20and%20return%20any%20warning.%0A%0A%20%20%20%20%20%20%20%20Assignment%20remains%20last%20so%20a%20recovered/new%20contributor%20cannot%20open%0A%20%20%20%20%20%20%20%20the%20item%20until%20its%20component%20value%20and%20Profile%20status%20are%20ready.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20already_assigned%20%3D%20is_profile_owned_by_or_unassigned%28%0A%20%20%20%20%20%20%20%20%20%20%20%20client%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20project_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20email%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%23%20Preserve%20a%20complete%20value%3B%20seed%20only%20a%20new%20or%20confirmed-missing%20one.%0A%20%20%20%20%20%20%20%20degraded_reason%20%3D%20initialize_or_preserve_profile_value%28%0A%20%20%20%20%20%20%20%20%20%20%20%20client%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20project_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20profile_folder_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20default_task_ids%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20created%3Dcreated%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20run%3Drun%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20%23%20Keep%20every%20successfully%20provisioned%20profile%20in%20the%20Profile%20status.%0A%20%20%20%20%20%20%20%20set_profile_status%28client%2C%20project_id%2C%20profile_folder_id%2C%20item_name%2C%20run%29%0A%20%20%20%20%20%20%20%20%23%20Assignment%20stays%20last.%20The%20ownership%20decision%20above%20is%20authoritative%0A%20%20%20%20%20%20%20%20%23%20for%20this%20run%3B%20an%20existing%20owner%20requires%20no%20assignment%20call.%0A%20%20%20%20%20%20%20%20if%20already_assigned%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20run.record%28%22assign_item%22%2C%20item%3Ditem_name%2C%20user%3Demail%2C%20already%3DTrue%29%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20assign_item%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20client%2C%20project_id%2C%20profile_folder_id%2C%20item_name%2C%20email%2C%20run%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20run.record%28%22profile_ensured%22%2C%20item%3Ditem_name%2C%20item_id%3Ditem_id%2C%20created%3Dcreated%29%0A%20%20%20%20%20%20%20%20return%20degraded_reason%0A%0A%20%20%20%20%23%201.%20Eligibility%20gate%3A%20only%20the%20exact%20%22Contributor%22%20role%20gets%20a%20profile.%0A%20%20%20%20%23%20Everything%20else%20is%20a%20recorded%20no-op%20that%20still%20returns%20a%20success%0A%20%20%20%20%23%20envelope%20%28duplicate/uninteresting%20deliveries%20are%20normal%20traffic%29.%0A%20%20%20%20if%20role_name%20%21%3D%20ELIGIBLE_ROLE_NAME%3A%0A%20%20%20%20%20%20%20%20log%28f%22Skipping%20%7Bemail%7D%3A%20role%20%7Brole_name%21r%7D%20is%20not%20%7BELIGIBLE_ROLE_NAME%21r%7D%22%29%0A%20%20%20%20%20%20%20%20run.record%28%22skip_ineligible_role%22%2C%20email%3Demail%2C%20role_name%3Drole_name%29%0A%20%20%20%20%20%20%20%20report%28eligible%3DFalse%2C%20created%3DFalse%29%0A%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20client%20%3D%20get_sa_client%28%29%0A%0A%20%20%20%20%23%202.%20Component%20config%20%E2%80%94%20required%20for%20the%20project%20to%20count%20as%20Smart%0A%20%20%20%20%23%20Workflow%3B%20also%20the%20source%20of%20the%20profiles-folder%20location.%0A%20%20%20%20config_data%20%3D%20read_config_data%28client%2C%20project_id%2C%20run%29%0A%20%20%20%20if%20config_data%20is%20None%3A%0A%20%20%20%20%20%20%20%20report%28%0A%20%20%20%20%20%20%20%20%20%20%20%20eligible%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20created%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20skip_reason%3D%22project_not_configured%22%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%23%202a.%20Freeze%20the%20config%27s%20default%20tasks%20for%20this%20run.%20Only%20a%20profile%20this%0A%20%20%20%20%23%20run%20creates%20%28or%20repairs%20from%20a%20completely%20missing%20value%29%20gets%20them%3B%20an%0A%20%20%20%20%23%20existing%20profile%20is%20never%20re-seeded%2C%20which%20is%20what%20makes%20a%20later%20config%0A%20%20%20%20%23%20change%20non-retroactive.%0A%20%20%20%20default_task_ids%20%3D%20resolve_default_task_ids%28config_data%2C%20run%29%0A%0A%20%20%20%20%23%203.%20Profiles%20folder%20%28resolved%20from%20config%20or%20created%20on%20first%20use%29.%0A%20%20%20%20folder_id%20%3D%20resolve_profiles_folder%28client%2C%20project_id%2C%20config_data%2C%20run%29%0A%0A%20%20%20%20%23%204.%20Idempotency%20gate%201%3A%20an%20item%20assigned%20to%20the%20user%20that%20carries%20the%0A%20%20%20%20%23%20profile%20flag%20means%20the%20profile%20already%20exists.%0A%20%20%20%20existence_query%20%3D%20%28%0A%20%20%20%20%20%20%20%20f%27assignment.Contributor%28email%20%3D%20%22%7Bemail%7D%22%29%27%0A%20%20%20%20%20%20%20%20f%22%20AND%20web_%7BUSER_PROFILE_COMPONENT_ID%7D%28userProfile%20%3D%20true%29%22%0A%20%20%20%20%29%0A%20%20%20%20log%28f%22Profile%20existence%20query%3A%20%7Bexistence_query%7D%22%29%0A%20%20%20%20team_id%20%3D%20get_team_id%28client%29%0A%20%20%20%20existing%20%3D%20query_items%28project_id%2C%20existence_query%2C%20limit%3D1%2C%20team_id%3Dteam_id%29%0A%20%20%20%20run.record%28%22existence_check%22%2C%20found%3Dlen%28existing%29%2C%20query%3Dexistence_query%29%0A%20%20%20%20if%20existing%3A%0A%20%20%20%20%20%20%20%20found%20%3D%20existing%5B0%5D%0A%20%20%20%20%20%20%20%20item_id%20%3D%20found.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20item_name%20%3D%20found.get%28%22name%22%29%0A%20%20%20%20%20%20%20%20found_folder%20%3D%20found.get%28%22folder_id%22%29%20or%20folder_id%0A%20%20%20%20%20%20%20%20log%28f%22Profile%20already%20exists%20for%20%7Bemail%7D%3A%20%7Bitem_name%21r%7D%22%29%0A%20%20%20%20%20%20%20%20degraded_reason%20%3D%20finalize_profile%28%0A%20%20%20%20%20%20%20%20%20%20%20%20item_id%2C%20item_name%2C%20found_folder%2C%20created%3DFalse%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20report%28%0A%20%20%20%20%20%20%20%20%20%20%20%20eligible%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20created%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_id%3Ditem_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_name%3Ditem_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20folder_id%3Dfound_folder%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20degraded%3Ddegraded_reason%20is%20not%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20degraded_reason%3Ddegraded_reason%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%23%205.%20Idempotency%20gate%202%3A%20a%20same-named%20item%20already%20in%20the%20profiles%20folder%0A%20%20%20%20%23%20also%20counts%20as%20%22exists%22%20%28the%20explore%20index%20can%20lag%20behind%20a%20just-made%0A%20%20%20%20%23%20creation%2C%20and%20a%20half-finished%20earlier%20delivery%20may%20have%20created%20the%0A%20%20%20%20%23%20item%20without%20assigning%20it%29.%20Adopt%20it%20instead%20of%20creating%20a%20duplicate.%0A%20%20%20%20item_name%20%3D%20normalize_email%28email%29%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20same_named%20%3D%20client.list_items%28project_id%2C%20folder_id%2C%20name%3Ditem_name%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20list%20items%20in%20folder%20%7Bfolder_id%7D%20of%20project%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bproject_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%0A%20%20%20%20if%20same_named%3A%0A%20%20%20%20%20%20%20%20item%20%3D%20select_profile_adoption_item%28same_named%2C%20item_name%2C%20email%29%0A%20%20%20%20%20%20%20%20item_id%20%3D%20item.get%28%22id%22%29%0A%20%20%20%20%20%20%20%20run.record%28%22adopt_existing_item%22%2C%20item%3Ditem_name%2C%20item_id%3Ditem_id%29%0A%20%20%20%20%20%20%20%20degraded_reason%20%3D%20finalize_profile%28%0A%20%20%20%20%20%20%20%20%20%20%20%20item_id%2C%20item_name%2C%20folder_id%2C%20created%3DFalse%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20report%28%0A%20%20%20%20%20%20%20%20%20%20%20%20eligible%3DTrue%2C%20created%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20item_id%3Ditem_id%2C%20item_name%3Ditem_name%2C%20folder_id%3Dfolder_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20degraded%3Ddegraded_reason%20is%20not%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20degraded_reason%3Ddegraded_reason%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%23%206.%20Create%20the%20profile%20item.%20attach_items%20keeps%20the%20exact%20item%20name%0A%20%20%20%20%23%20%28generate_items%20appends%20numeric%20suffixes%29%3B%20the%20url%20is%20never%20dereferenced%0A%20%20%20%20%23%20for%20these%20form-based%20multimodal%20items.%20annotation_status%20sets%20the%0A%20%20%20%20%23%20workflow%20status%20at%20creation.%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20uploaded%2C%20fails%2C%20duplicated%20%3D%20client.attach_items%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%28project_id%2C%20folder_id%29%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%7B%22name%22%3A%20item_name%2C%20%22url%22%3A%20item_name%7D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20annotation_status%3DPROFILE_ANNOTATION_STATUS%2C%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20create%20profile%20item%20%7Bitem_name%21r%7D%20in%20folder%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bfolder_id%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20if%20fails%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22profile%20item%20%7Bitem_name%21r%7D%20failed%20to%20attach%3A%20%7Bfails%21r%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%23%20A%20duplicate%20in%20the%20same%20instant%20means%20a%20concurrent%20delivery%20created%20the%0A%20%20%20%20%23%20item%20first%20%E2%80%94%20treat%20it%20as%20existing%20and%20do%20not%20overwrite%20its%20value.%0A%20%20%20%20created_now%20%3D%20bool%28uploaded%29%0A%20%20%20%20run.record%28%0A%20%20%20%20%20%20%20%20%22create_item%22%2C%0A%20%20%20%20%20%20%20%20item%3Ditem_name%2C%0A%20%20%20%20%20%20%20%20folder_id%3Dfolder_id%2C%0A%20%20%20%20%20%20%20%20duplicated%3Dbool%28duplicated%29%2C%0A%20%20%20%20%29%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20created_items%20%3D%20client.list_items%28project_id%2C%20folder_id%2C%20name%3Ditem_name%29%0A%20%20%20%20except%20Exception%20as%20error%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22could%20not%20read%20back%20created%20profile%20item%20%7Bitem_name%21r%7D%3A%20%7Berror%7D%22%0A%20%20%20%20%20%20%20%20%29%20from%20error%0A%20%20%20%20if%20not%20created_items%3A%0A%20%20%20%20%20%20%20%20raise%20ExternalServiceError%28%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22profile%20item%20%7Bitem_name%21r%7D%20was%20attached%20but%20cannot%20be%20found%20in%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22folder%20%7Bfolder_id%7D%22%0A%20%20%20%20%20%20%20%20%29%0A%20%20%20%20%23%20Select%20defensively%20even%20after%20a%20successful%20attach%3A%20a%20concurrent%0A%20%20%20%20%23%20same-name%20profile%20must%20never%20be%20selected%20merely%20because%20it%20appears%20first.%0A%20%20%20%20item%20%3D%20select_profile_adoption_item%28created_items%2C%20item_name%2C%20email%29%0A%20%20%20%20item_id%20%3D%20item.get%28%22id%22%29%0A%0A%20%20%20%20degraded_reason%20%3D%20finalize_profile%28%0A%20%20%20%20%20%20%20%20item_id%2C%20item_name%2C%20folder_id%2C%20created%3Dcreated_now%0A%20%20%20%20%29%0A%20%20%20%20log%28%0A%20%20%20%20%20%20%20%20f%22Profile%20%7B%27created%27%20if%20created_now%20else%20%27ensured%27%7D%20for%20%7Bemail%7D%3A%20%22%0A%20%20%20%20%20%20%20%20f%22%7Bitem_name%21r%7D%20%28item%20%7Bitem_id%7D%2C%20folder%20%7Bfolder_id%7D%29%22%0A%20%20%20%20%29%0A%20%20%20%20report%28%0A%20%20%20%20%20%20%20%20eligible%3DTrue%2C%20created%3Dcreated_now%2C%0A%20%20%20%20%20%20%20%20item_id%3Ditem_id%2C%20item_name%3Ditem_name%2C%20folder_id%3Dfolder_id%2C%0A%20%20%20%20%20%20%20%20degraded%3Ddegraded_reason%20is%20not%20None%2C%0A%20%20%20%20%20%20%20%20degraded_reason%3Ddegraded_reason%2C%0A%20%20%20%20%29%0A",
        "config": {
          "interpreter": "3.11",
          "requirements": [
            "superannotate"
          ]
        },
        "memory": 128,
        "time_limit": 900,
        "concurrency": 32
      }
    },
    "$role_1": {
      "type": "role",
      "label": "Contributor",
      "description": "The role must be named exactly 'Contributor'",
      "source_nodes": [
        3,
        4
      ],
      "source_status_icon": "person",
      "source_role_name": "Contributor"
    }
  }
}