Okay, we're going to make a delivery plan based on this system I only kinda understand. Here's where we are.
https://thrivemarket.atlassian.net/wiki/spaces/EN/pages/4080009244/Future+autoship+how+we+might+get+there the big idea https://thrivemarket.atlassian.net/wiki/spaces/EN/pages/4097310741/Autoship+Migration+Phase+1+Airflow+DAGs+to+Temporal+Workflows - the current plan
My misconception: I had a misunderstanding about the autoship thing we were building. Right now, we've got a bunch of DAGs that were migrated over to temporal workflows. Those workflows call into the CMS API. The logic is that every ~20 minutes, we wake up.. query for a list of users that have things that need done.. then execute the activities (a temporal concept which is ~= function) to process those things.
I thought we had a single long lived workflow per customer, with a run per "schedule", so you'd see the t-3, prefill, etc events under a single "Justin's August 14th order" type thing. That's apparently phase 3, which will happen after autoship-service is the source of truth for both reads and writes.
The work to make that happen is largely in orchestration and isn't expected to be a big PITA, because the activities themselves will have identical logic to do the needful to fulfill what a customer needs.
Sharing here in case others had the same misunderstanding as me. Also, if it wasn't clear.. I think this is a totally fine way of things being.
A recent meeting's notes: Notes Decision: Is there a time where one team’s service is reading or writing from another team’s (or shared) database? Aligned Preference: integrate over APIs for both reads & writes and almost never directly from a data source No, there is not a good time for a team to be writing to another team’s data store. Some exceptions might exist where reads are OK for a known period of time, but why not do it over API. Magento specific problem statement: There is no “clear” pattern of ownership, coupling. So how do we read/migrate data that lives in Magento (e.g. Monoith). Question: Should we move Autoship to its own service when we can’t really decouple the Magento database? What benefits are we looking to gain from carving out a standalone Autoship service? Iteration speed, change confidence through clear domain boundaries, performance/speed which impacts scale, fewer bugs in a service Key Principles: De-coupling business logic Clear interfaces between Autoship processes & other services (e.g. Magento) What benefits are we looking to gain from carving out a standalone Autoship service? Domain Boundaries Autoship: At least Autoship will “own” user_id, item_id, frequency Orchestration of prefilling and committing order in Magento >> Temporal Expectations from other domains:
- Add the scheduled stuff to the user’s cart at the correct time. /cms-api/add-to-cart (or something), owned by buy-and-track
- Check out the user’s cart at the correct scheduled time. /cms-api/checkout (or something) owned by buy-and-track Migration Sequencing
- Autoship service is deployed (done), clients do not call it directly
- There exists orchestrators which call cms-api (?)
- Orchestrators call Autoship Read-API, “joins” local db and Magento for: user_id, item_id, frequency Data warehouse too^^ How to Handle Writes Preferred (needs planning/investigation across teams): Option 1 Migrate all readers to this new API
- Writes start going to autoship api (not magento) This involves client changes. Benefits: Clear end state, line in the sand You get the benefits faster Option 2 Dual write to magento & new DB Convert readers piece by piece Stop dual writing Benefits: Can show progress more quickly
- Back fill data from magento to autoship
- Stop reading magento tables Follow ups questions: Write down specifically, all the autoship domain data (e.g. columns). Who are all the readers to the Autoship domain data and where do they call? What work do we need other teams to do? Buy & Track admin apis for cart add & checkout Need to understand the contract for data from data warehouse What do we need to handle for Prefill NOW and Ship NOW versus orchestration-driven prefills/orders? For example handling of subsequent schedules, etc Possibly, B&T needs to tell Autoship “Ship Now” happened… Document and share Autoship interface Bypass Magento as Auth-Gateway, will need FE-Core to pass through Get your JWT token from Justin-Auth-Service Keep it refreshed Dependency on Clients to pass-through JWT tokens
Look at all child tickets (and their child tickets) of https://thrivemarket.atlassian.net/browse/TM-364
We need to come up with a plan. Broad strokes, we have:
- a public API that clients will call. We need to build this out and then frontends need to use it.
- A private internal API that's called by the autoship temporal stuff (which live in ../magento-temporal-processors)
- We need to get to a place where magento (../magento) can read from autoship using the admin API in a we-hope-to-not-support-this-forever kind of way.
Right now magento is the source of truth for the data. We want it to be the service at $PWD.
We think we probably need to grow read apis.. then migrate all users/services to those read apis. This includes the temporal workers. Then put writes behind a feature flag. We'll probably then take some downtime, and move those writes to autoship service directly (backend only, I think).
We also know we need an admin UI for the member services people as well as figure out how the data people need to operate.
Can you make an approximate project plan from all of this?
⏺ I'll gather information from the Confluence pages, Jira tickets, and codebase to build a comprehensive project plan.
⏺ mcp__atlassian__confluence_get_page(...) ⎿ [{'type': 'text', 'text': '{\n "metadata": {\n "id": "4080009244",\n "title": "Future autoship & how we might get there",\n "type": "page",\n "created": "",\n "updated": "",\n "url": "https://thrivemarket.atlassian.net/wiki/spaces/EN/pages/4080009244",\n "space": {\n "key": "EN",\n "name": "Engineering"\n },\n "version": 4,\n "attachments": [\n {\n "id": "att4080009262",\n "type": "attachment",\n "status": "current",\n "title": "Screenshot 2025-09-03 at 2.50.54\u202fPM.png",\n "media_type": "image/png",\n "file_size": 72339\n },\n {\n "id": "att4080336906",\n "type": "attachment",\n "status": "current",\n "title": "Screenshot 2025-09-03 at 2.45.20\u202fPM.png",\n "media_type": "image/png",\n "file_size": 94672\n },\n {\n "id": "att4080009253",\n "type": "attachment",\n "status": "current",\n "title": "Screenshot 2025-09-03 at 2.42.56\u202fPM.png",\n "media_type": "image/png",\n "file_size": 56683\n }\n ],\n "content": {\n "value": "This is not a design doc, per se. It’s a pseudo design doc which sets a possible direction. There will be future design docs about each step in this process. Feedback welcome!\n\n@Tuyen Le and I were talking about redesigning autoship. We need to understand what the farther future might look like so we know how to evaluate the intermediate steps. Here’s what we came up with.\n\nToday\n-----\n\nAutoship as it exists today\n\nRight now, a user interacts w/ MOA. MOA writes the user’s schedule into the db. On a periodic basis, airflow triggers which will ask CMS API for a list of schedules that need processing, then fan out and call CMS API to “process this one schedule” as many times as needed. This is primarily to get around threading limitations in PHP.\n\nThere are multiple DAGs. I don’t understand them.. but I know that 1 adds stuff to cart and another checks the user out.. but they don’t coordinate so we could technically do a checkout before we’ve added the stuff to the user’s cart.\n\nThe future\n----------\n\nWhat the future might look like\n\nIn the future, we’ll have an autoship service. It’ll be responsible for managing the mapping of users to their shipment schedules and which items they want in those orders. There will be an async temporal service for each user/schedule pairing, enqueued until it’s next expected runtime. When it wakes up, it’ll ask for the items that the user needs to order, and add them to the user’s cart with a CMS API call (after ensuring those items are available). It will then check out for that user with another CMS API call and the user will get their cool stuff.\n\nHow do we get there?\n--------------------\n\nExtract temporal, but replicate current functionality\n\nTo migrate to the future good thing, we’ll first start with replacing those 3 dags that I don’t understand with a single temporal workflow. It’ll ensure that all the steps are done in order each time. As an implementation detail, we’ll have the way that it works (batch) delegate to a subworkflow that operates on single-user/schedule pairs. This will allow us to migrate to a single workflow per user/schedule when we eventually deprecate batch.\n\nWe’re going to then migrate from “Give me all the schedules, process this one schedule \\* N” to one where we do the schedule processing in temporal. It will add the requisite things directly to the cart via CMS API. When those are complete, it will call place order on the CMS API.\n\nOnce that’s done, we’ll pull the autoship scheduling into it’s own service. During the migration, we’ll do a dual-write thing. Users will speak to the autoship service for viewing/editing their schedules. This will also migrate the data to an “autoship-service owned” database. No one else (aside from data team’s fivetran stuff) is allowed access to that db. Temporal will begin asking the autoship service for the schedules to process, rather than the CMS API.\n\nLastly, we’ll migrate from a batch-oriented temporal flow to one where we just make a new temporal run for every user/schedule pair. Those workflows will wait around for the 4 weeks or whatever.. then wake up and do the stuff they need done.\n\nOpen questions\n--------------\n\n1. Is there\xa0common\xa0logic\xa0between\xa0magento\xa0&\xa0temporal\xa0processing? Where will that live in the medium term?\n2. Can\xa0the\xa0APIs\xa0we\xa0need\xa0to\xa0call\xa0(recommenders,\xa0etc)\xa0support\xa0non-batch\xa0cases?",\n "format": "markdown"\n }\n }\n}'}]
⏺ mcp__atlassian__confluence_get_page(...)
⎿ [{'type': 'text', 'text': '{\n "metadata": {\n "id": "4097310741",\n "title": "Autoship Migration (Phase 1): Airflow DAGs to Temporal Workflows",\n "type": "page",\n "created": "",\n "updated": "",\n "url": "https://thrivemarket.atlassian.net/wiki/spaces/EN/pages/4097310741",\n "space": {\n "key": "EN",\n "name": "Engineering"\n },\n "version": 7,\n "attachments": [\n {\n "id": "att4101439512",\n "type": "attachment",\n "status": "current",\n "title": "dagautoshipmigration.png",\n "media_type": "image/png",\n "file_size": 76249\n },\n {\n "id": "att4101603350",\n "type": "attachment",\n "status": "current",\n "title": "dagautoshipmigration",\n "media_type": "application/gliffy+json",\n "file_size": 38777\n }\n ],\n "content": {\n "value": "Proposal: Migrate autoship processing from 3 interdependent Airflow DAGs to a unified Temporal workflow system.\n\nCurrent Scale: Processing 25k+ schedules daily (based on production Slack reports)\n\nAutoship Processing Migration: Airflow DAGs to Temporal Workflows\n=================================================================\n\nExecutive Summary\n-----------------\n\nProposal: Migrate autoship processing from 3 interdependent Airflow DAGs to a unified Temporal workflow system.\n\nCurrent Scale: Processing 25k+ schedules daily (based on production Slack reports)\n\nCurrent State\n-------------\n\nCurrent Architecture:\n\n* 3 separate DAGs (cronjobs like) running in scheduled:\n\n + autoship\\_single\\_cart\\_move\\_items (based on schedule ids) → XCOM (queue) -> autoship\\_prefill\\_recs\\_master\\_cart(paused)\n + autoship\\_schedule\\_process\\_trigger (based on customer id)\n* XCom dependencies between DAGs for data passing\n* autoship\\_schedule\\_process\\_trigger runs every 20 minutes with max\\_active\\_runs=1\n* Processing limits: 100 schedules per run (move items), 500 customers per run (AROPE)\n\nTechnical Dependencies:\n\n* autoship\\_prefill\\_recs\\_master\\_cart reads schedule\\_ids from autoship\\_single\\_cart\\_move\\_items XCom\n* CMS customers endpoint filters for is\\_prefilled=true (requires prefill step completion)\n* Potential timing issues between move-items completion and AROPE execution\n\nProposed Solution\n-----------------\n\n### Phase 1: Unified Per-Customer Workflow\n\nArchitecture: Single Temporal workflow per customer containing all 3 DAG functions:\n\natt4101439512205c1daf-fcc3-442f-84d8-d2440414e39ahttps://thrivemarket.atlassian.net/wikidagautoshipmigrationatt4101603350409731074111758058353102\n\nOrchestrator:\n\n* Scheduled workflow (runs every 20 minutes)\n* Gets customers from database along with their schedules\n* Starts to run workflows in batches (each workflow is a schedule with id customer_{id}_schedule_{id})\n* Passes only customer ID and schedule ID to each workflow\n\nCustomer Schedule Workflow:\n\n* Short-lived workflow (minutes, not persistent)\n* Receives customer ID and schedule ID parameter\n* Calls \"Fetch schedules\" activity\n* Then calls Prefill + Place Orders activities in parallel\n* Completes and terminates\n\nActivities:\n\n* Fetch schedules: Gets all schedules for that customer\n* Prefill: Processes schedules needing prefill (3 days before ship)\n* Place Orders: Processes schedules needing orders (on/after ship date)\n\nKey Benefits:\n\n* Eliminates XCom coupling between separate DAGs\n* Ensures correct processing order per customer\n* Per-customer isolation prevents cross-customer interference\n* Built-in Temporal retry mechanisms and deterministic replay\n\nComparison Table between current architecture and Temporal migration\n\n| Aspect | Current (Airflow + API) | With Temporal |\n| --- | --- | --- |\n| HTTP Timeouts | ❌ 90s limit, frequent timeouts | ✅ No HTTP layer, unlimited duration |\n| Retry Logic | ❌ Manual, batch-level only | ✅ Automatic, per-schedule with backoff |\n| Observability | ❌ Logs only, poor visibility | ✅ Built-in UI, real-time status |\n| Race Conditions |
⏺ mcp__atlassian__jira_get_issue(...) ⎿ [{'type': 'text', 'text': '{\n "content": {\n "id": "417974",\n "key": "comment",\n "summary": "Recurring Foundations ",\n "description": null,\n "created": "2026-01-08 06:51:09",\n "updated": "2026-01-08 06:51:09",\n "status": {\n "name": "In Progress",\n "category": "In Progress",\n "color": "yellow"\n },\n "issue_type": {\n "name": "Initiative",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium"\n },\n "priority": {\n "name": "Not Prioritized",\n "icon_url": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg"\n },\n "assignee": null,\n "reporter": {\n "display_name": "rk.sharma",\n "name": "rk.sharma",\n "email": "rk.sharma@thrivemarket.com",\n "avatar_url": "https://secure.gravatar.com/avatar/8eb6279d9a9cdad401299aacd2708189?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FR-6.png"\n },\n "labels": [\n "pod-habit-formation"\n ],\n "components": [\n "Autoship Program (habit-formation)"\n ],\n "comments": [],\n "attachments": [],\n "url": "https://thrivemarket.atlassian.net/browse/TM-364",\n "epic_key": null,\n "epic_name": null,\n "fix_versions": [],\n "customfield_14157": null,\n "customfield_13861": null,\n "customfield_13860": null,\n "customfield_13859": null,\n "customfield_14025": null,\n "customfield_12000": null,\n "customfield_12002": null,\n "customfield_12001": [],\n "customfield_12400": null,\n "customfield_12004": [],\n "customfield_12003": null,\n "customfield_13733": null,\n "customfield_13732": null,\n "customfield_11700": null,\n "customfield_13845": null,\n "customfield_13727": null,\n "customfield_15224": null,\n "customfield_13841": null,\n "customfield_11300": "{}",\n "customfield_13843": null,\n "customfield_13842": null,\n "customfield_13834": "RICE Score: 0",\n "customfield_12901": null,\n "customfield_12900": null,\n "customfield_13959": null,\n "customfield_12903": null,\n "customfield_12902": null,\n "customfield_12905": null,\n "customfield_13791": null,\n "customfield_13790": null,\n "customfield_13793": null,\n "customfield_13792": null,\n "customfield_12101": null,\n "customfield_13797": null,\n "customfield_13796": null,\n "customfield_10200": null,\n "customfield_12102": null,\n "customfield_13799": [],\n "customfield_12500": null,\n "customfield_13823": null,\n "customfield_13789": null,\n "customfield_13780": null,\n "customfield_13782": null,\n "customfield_13781": null,\n "customfield_13784": null,\n "customfield_11001": null,\n "customfield_13783": null,\n "customfield_13786": null,\n "customfield_13785": null,\n "customfield_11401": null,\n "customfield_13304": null,\n "customfield_13700": null,\n "customfield_11400": null,\n "customfield_13787": null,\n "customfield_13820": null,\n "customfield_13779": null,\n "customfield_13778": null,\n "customfield_13814": null,\n "customfield_13816": null,\n "customfield_13815": null,\n "customfield_13818": null,\n "customfield_13817": null,\n "customfield_13819": null,\n "customfield_13892": null,\n "customfield_13771": null,\n "customfield_13770": null,\n "customfield_13773": null,\n "customfield_13772": null,\n "customfield_13775": null,\n "customfield_13774": null,\n "customfield_10300": null,\n "customfield_13777": null,\n "customfield_10301": null,\n "customfield_14105": null,\n "customfield_13776": null,\n "customfield_13768": 0.0,\n "customfield_13767": 0.0,\n "customfield_13802": null,\n "customfield_13769": 1.0,\n "customfield_13880": null,\n "customfield_13883": null,\n "customfield_13762": null,\n "customfield_13882": null,\n "customfield_10012": "0|i1p4zu:",\n "customfield_13766": 0.0,\n "customfield_13765": 0.0,\n "customfield_10401": null,\n "customfield_13757": null,\n "customfield_10005": null,\n "customfield_13877": null,\n "customfield_10007": null,\n "customfield_10800": null,\n "customfield_10009": null,\n "customfield_13874": null,\n "customfield_13753": null,\n "customfield_12300": null,\n "customfield_10003": null,\n "customfield_13876": null,\n "customfield_13867": null,\n "customfield_11600": null,\n "customfield_13866": null,\n "customfield_13749": null,\n "statuscategorychangedate": "2026-01-08T06:51:09.684-0800",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n },\n "lastViewed": "2026-02-03T19:40:55.824-0800",\n "versions": [],\n "issuelinks": [],\n "creator": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/user?accountId=712020%3A271b87a0-ae48-4c5b-8da6-6534d59a228e",\n "accountId": "712020:271b87a0-ae48-4c5b-8da6-6534d59a228e",\n "emailAddress": "rk.sharma@thrivemarket.com",\n "avatarUrls": {\n "48x48": "https://secure.gravatar.com/avatar/8eb6279d9a9cdad401299aacd2708189?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FR-6.png",\n "24x24": "https://secure.gravatar.com/avatar/8eb6279d9a9cdad401299aacd2708189?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FR-6.png",\n "16x16": "https://secure.gravatar.com/avatar/8eb6279d9a9cdad401299aacd2708189?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FR-6.png",\n "32x32": "https://secure.gravatar.com/avatar/8eb6279d9a9cdad401299aacd2708189?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FR-6.png"\n },\n "displayName": "rk.sharma",\n "active": true,\n "timeZone": "America/Los_Angeles",\n "accountType": "atlassian"\n },\n "subtasks": [],\n "aggregateprogress": {\n "progress": 0,\n "total": 0\n },\n "progress": {\n "progress": 0,\n "total": 0\n },\n "votes": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/TM-364/votes",\n "votes": 0,\n "hasVoted": false\n },\n "project": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/project/13156",\n "id": "13156",\n "key": "TM",\n "name": "Thrive Market Engineering",\n "projectTypeKey": "software",\n "simplified": false,\n "avatarUrls": {\n "48x48": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/14901",\n "24x24": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/14901?size=small",\n "16x16": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/14901?size=xsmall",\n "32x32": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/14901?size=medium"\n }\n },\n "workratio": -1,\n "watches": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/TM-364/watchers",\n "watchCount": 1,\n "isWatching": false\n },\n "timetracking": {}\n }\n}'}]
⏺ mcp__atlassian__jira_search(...) ⎿ [{'type': 'text', 'text': '[\n {\n "id": "360322",\n "key": "status",\n "summary": "Migrate Autoship DAGs to Temporal ",\n "status": {\n "name": "6. Development In Progress",\n "category": "In Progress",\n "color": "yellow"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "415167",\n "key": "status",\n "summary": "Migrate Prefill API from Magento to Autoship Service",\n "status": {\n "name": "4. Engineering Design",\n "category": "To Do",\n "color": "blue-gray"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "419087",\n "key": "status",\n "summary": "Technical Delivery Plan for Autoship Service APIs and Migrations",\n "status": {\n "name": "5. Delivery Plan",\n "category": "To Do",\n "color": "blue-gray"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "419237",\n "key": "status",\n "summary": "Improve Autoship Scheduling capabilities + {engineering improvement TBD}",\n "status": {\n "name": "2. Feature Scoping",\n "category": "To Do",\n "color": "blue-gray"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "425385",\n "key": "status",\n "summary": "Change Prefill + Order Processing time to customer's local timezone (using shipping address) ",\n "status": {\n "name": "6. Development In Progress",\n "category": "In Progress",\n "color": "yellow"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "426068",\n "key": "status",\n "summary": "Migrate Autoship Notifications to Autoship service with increased reliability",\n "status": {\n "name": "4. Engineering Design",\n "category": "To Do",\n "color": "blue-gray"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "426070",\n "key": "status",\n "summary": "Implement client (user)-facing APIs in Autoship service",\n "status": {\n "name": "4. Engineering Design",\n "category": "To Do",\n "color": "blue-gray"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "426076",\n "key": "status",\n "summary": "Improved Autoship Status Handling",\n "status": {\n "name": "2. Feature Scoping",\n "category": "To Do",\n "color": "blue-gray"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n },\n {\n "id": "426081",\n "key": "status",\n "summary": "Migrate Order Processing API to Autoship Service",\n "status": {\n "name": "4. Engineering Design",\n "category": "To Do",\n "color": "blue-gray"\n },\n "issuetype": {\n "name": "Epic",\n "icon_url": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10307?size=medium"\n },\n "parent": {\n "id": "417974",\n "key": "TM-364",\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issue/417974",\n "fields": {\n "summary": "Recurring Foundations ",\n "status": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/status/3",\n "description": "This work item is being actively worked on at the moment by the assignee.",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/statuses/inprogress.png",\n "name": "In Progress",\n "id": "3",\n "statusCategory": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/statuscategory/4",\n "id": 4,\n "key": "indeterminate",\n "colorName": "yellow",\n "name": "In Progress"\n }\n },\n "priority": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/priority/10000",\n "iconUrl": "https://thrivemarket.atlassian.net/images/icons/priorities/blocker_new.svg",\n "name": "Not Prioritized",\n "id": "10000"\n },\n "issuetype": {\n "self": "https://thrivemarket.atlassian.net/rest/api/2/issuetype/10813",\n "id": "10813",\n "description": "high-level priorities that multiple teams or departments work against.",\n "iconUrl": "https://thrivemarket.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10300?size=medium",\n "name": "Initiative",\n "subtask": false,\n "avatarId": 10300,\n "hierarchyLevel": 2\n }\n }\n }\n }\n]'}]
⏺ Let me get more details on the epics and explore the current codebase structure.
⏺ mcp__atlassian__jira_get_epic_issues(...) ⎿ [{'type': 'text', 'text': 'Error: Issue TM-381 is not an Epic, it is a Initiative'}]
⏺ mcp__atlassian__jira_get_epic_issues(...) ⎿ [{'type': 'text', 'text': 'Error: Issue TM-499 is not an Epic, it is a Debt'}]
⏺ mcp__atlassian__jira_get_epic_issues(...) ⎿ [{'type': 'text', 'text': 'Error: Issue TM-534 is not an Epic, it is a Feature'}]
⏺ Task(Explore autoship codebase)
⎿ [{'type': 'text', 'text': 'Perfect! I now have a comprehensive understanding of the codebase. Let me provide the complete summary:\n\n---\n\n## Autoship Service: Current State Summary\n\n### 1. Service Architecture Overview\n\nLanguage & Framework:\n- Python 3.13+ with Connexion 3.x (OpenAPI-first Flask wrapper)\n- Async SQLAlchemy with aiomysql for database access\n- Uvicorn ASGI server (port 8000 for public API, port 8001 for private/health)\n\nKey Dependencies:\n- Connexion for OpenAPI spec-driven routing\n- SQLAlchemy ORM with async support\n- OpenFeature SDK for feature flag management (flagd provider)\n- PyJWT with ES384 for JWT authentication\n- OpenTelemetry for comprehensive distributed tracing\n- thrive-foundations (internal library) for logging and settings\n\n### 2. API Structure\n\nPublic API (Port 8000) - /autoship prefix\n- Health check endpoint (public, no auth required)\n- Subscriptions CRUD (list, create, get, update, delete)\n- Subscription Items management\n- Schedules management\n- Actions (prefill operations)\n\nPrivate API (Port 8001) - Health/Readiness checks\n- /health - Service health with optional DB connectivity check\n- /ready - Readiness probe for Kubernetes\n\nAdmin API - Separate internal endpoints (in openapi/autoship-admin-api.yaml)\n- Schedules CRUD for Temporal workflows\n- Carts operations\n- Validation and notifications\n- Base path: /api/v1/autoship\n\nAuthentication:\n- Bearer token via JWT (ES384 algorithm)\n- JWKS fetched from auth service with 5-minute cache\n- Claims include: customer_id, email, group_id, scopes, security_level\n- Token validation in app/auth.py via bearer_auth() handler\n\n### 3. Database Models & Storage\n\nPrimary Database: Autoship-specific database (new system)\n- Connection: MySQL via DATABASE_URL config\n- Models defined in app/models/ directory with SQLAlchemy ORM\n\nLegacy Database (Read-Only): Magento database\n- Connection: MySQL via MAGENTO_DATABASE_URL config\n- Models defined in app/models/magento/ (read-only mapped models)\n- Used for data migration validation and dual-read scenarios\n\nKey Tables (Primary Database):\n\n| Table | Purpose |\n|-------|---------|\n| thrive_autoship | Main subscription record (customer_id, status) |\n| thrive_autoship_item | Items in subscription (product_id, qty, frequency_weeks, locked_price) |\n| thrive_autoship_schedule | Scheduled deliveries (delivery_date, totals, charge status) |\n| thrive_autoship_schedule_item | Items within a schedule |\n| thrive_autoship_event | Audit log of events |\n| thrive_autoship_history | History/changelog tracking |\n| thrive_autoship_notification | Notification records |\n| thrive_autoship_snapshot | Point-in-time snapshots |\n| thrive_autoship_order | Linked order records |\n| thrive_autoship_product_config | Product-level configuration |\n\nData Model Enums:\n- AutoshipStatus: ACTIVE (1), PAUSED (2), CANCELED (3)\n- ItemStatus: ACTIVE (1), CANCELED (2), DISCONTINUED (3), DISCONTINUED_ACTIVE_AGAIN (4)\n- ScheduleStatus: ACTIVE (1), PROCESSING (2), SHIPPING (3), COMPLETED (4), FAILED_CHARGE_1-3 (5-7), CANCELED (8)\n- ScheduleItemStatus: ACTIVE (1), SKIPPED (2), REMOVED (3)\n- FrequencyWeeks: 1, 2, 4, 8, 12 weeks\n\n### 4. Feature Flags & Migration Infrastructure\n\nOpenFeature Integration:\n- Flag name: autoship-data-source\n- Modes: autoship (new DB only), magento (legacy DB only), both (dual-read with discrepancy detection)\n- Default: magento (safe fallback during migration)\n- Implementation in app/features.py\n\nData Source Routing (NEW - Recent Implementation):\n\nget_data_source_mode() → Returns DataSourceMode from feature flag\n\nBehavior:\n- AUTOSHIP mode: Reads from new database only\n- MAGENTO mode: Reads from legacy Magento database only\n- BOTH mode: Reads from both, logs discrepancies, returns primary DB data if exists (legacy if not)\n\n\nDiscrepancy Detection:\n- Logs warnings when primary and legacy databases differ\n- Metrics: autoship.datasource.discrepancy.count counter\n- Helps validate data migration consistency\n\nRepository Pattern:\n- AutoshipRepository (in app/repositories/autoship.py) handles data source routing\n- Methods: get_by_id(), get_by_customer_id(), list_active(), create(), update(), add_item(), add_schedule()\n- All writes always go to primary database\n- Reads respect feature flag for source selection\n\n### 5. Current API Implementation Status\n\nImplemented:\n- Health check endpoint ✅\n- JWT authentication with bearer tokens ✅\n- OpenAPI spec-driven routing ✅\n- Database session management (async) ✅\n- Feature flag evaluation ✅\n- Structured JSON logging ✅\n- OpenTelemetry instrumentation ✅\n\nStubbed/Not Implemented (501 responses):\n- List subscriptions\n- Create subscription\n- Get subscription by ID (partially - Magento-only read exists)\n- Update subscription\n- List/create/get/update/delete items\n- List/create/get/update/delete schedules\n- Prefill actions\n\nHandler Pattern:\npython\n# All API handlers return ConnexionResponse or dict\ndef operation_name(params) -> ConnexionResponse:\n return not_implemented("resource", "Operation")\n\n\n### 6. Configuration & Environment\n\nConfig System: app/config.py using thrive.settings dataclass pattern\n\nKey Environment Variables:\n\nDATABASE_URL # Autoship DB (MySQL)\nMAGENTO_DATABASE_URL # Magento DB (MySQL) - optional\nAUTH_SERVICE_JWKS_URL # JWT key endpoint\nJWT_ISSUER / JWT_AUDIENCE # JWT validation\nFLAGD_HOST / FLAGD_PORT # Feature flag daemon\nFLAGD_ENABLED # Enable/disable feature flags\nLOG_LEVEL # Logging level\nENVIRONMENT / SERVICE_VERSION # Metadata\n\n\n### 7. Magento/CMS Integration\n\nMagento Module: Thrive_Autoship (Magento 1)\n- Legacy autoship implementation in Magento database\n- Read-only mapping via ORM models\n- Gradual migration to Autoship Service\n\nIntegration Pattern:\n1. Service reads from both databases during migration\n2. Feature flag controls which database is authoritative\n3. Write operations always go to new service database\n4. Discrepancy logging for data validation\n\nExternal Dependencies (from specs):\n- Braintree: Payment processing and vault\n- Sailthru: Email notifications\n- Attentive: SMS notifications\n- Customer Score Service: ML-powered recommendations\n- AROPE: Asynchronous Recurring Order Processing Engine (converts schedules to orders)\n\n### 8. Observability & Monitoring\n\nStructured Logging:\n- All logs via thrive.logging.get_logger()\n- JSON format with structured fields\n- Contextual data: request_id, user_id, operation, timestamps\n\nOpenTelemetry Instrumentation:\n- ASGI middleware for request tracing\n- Database query tracing\n- Async operation instrumentation\n- Exporter: OTLP to collector\n\nMetrics:\n- Discrepancy counter: autoship.datasource.discrepancy.count\n- (Framework metrics via OTEL)\n\n### 9. Testing & Quality\n\nTest Coverage:\n- Minimum 70% required (configured in pyproject.toml)\n- Uses pytest with pytest-asyncio\n- Coverage reports: HTML + XML (coverage.xml)\n\nCode Quality:\n- Type checking: mypy with strict mode\n- Linting: ruff\n- Security: bandit (skips B101, B104)\n- Pre-commit hooks configured (git push validation)\n\n### 10. Specification-Driven Development (OpenSpec)\n\nActive Change Proposals:\n1. add-data-source-feature-flag - Dual-read migration support (CURRENT BRANCH)\n2. add-temporal-notification-workflows - Notification orchestration planning\n\nExisting Specs:\n- Subscription Management\n- Item Management\n- Schedule Management\n- Order Processing\n- Fresh/Frozen Handling\n- Notifications\n- Admin Operations\n- Buy-Track Integration\n- Validation & Communication\n- Observability\n\n### 11. Recent Work & Current Branch\n\nCurrent Branch: add-data-source-feature-flag\n\nRecent Commits:\n1. 60a5927: Use thrive.logging instead of stdlib logging\n2. 501b248: Implement OpenFeature data source routing ← Feature flag implementation\n3. 119c61b: Add discrepancy logging and clarify targeting scope\n4. 0f868d4: Add OpenSpec proposal for data source feature flag\n5. 05230ab: Add AUTH_SERVICE_JWKS_URL for JWT validation\n\nKnown Issues/TODOs:\n- OTEL tracing not appearing in New Relic (PR #54 addresses uvicorn workers parameter)\n- Many API endpoints still return 501 Not Implemented\n- Awaiting approval on notification workflow proposal\n\n### 12. Project Structure\n\n\nautoship-service/\n├── app/\n│ ├── api/ # Request handlers\n│ ├── models/ # SQLAlchemy ORM (primary DB)\n│ ├── models/magento/ # Read-only Magento models\n│ ├── repositories/ # Data access layer\n│ ├── auth.py # JWT validation\n│ ├── config.py # Config management\n│ ├── features.py # OpenFeature flag client\n│ ├── db.py # Database session management\n│ ├── web.py # Public API (Connexion)\n│ └── private_web.py # Health/ready endpoints\n├── openapi/ # OpenAPI specs\n├── openspec/ # Change proposals & specs\n├── k8s/ # Kubernetes manifests\n├── docs/ # Documentation\n└── scripts/ # Utility scripts\n\n\n---\n\n## Key Takeaways\n\nThis is a migration-in-progress service that's being built to replace Magento-based autoship functionality. The current state reflects:\n\n1. Dual-read architecture supporting gradual migration from Magento\n2. Feature flag-driven routing to control data source (new DB vs legacy DB)\n3. Partial API implementation with most endpoints stubbed as 501 Not Implemented\n4. Strong observability foundation (OTEL, structured logging, metrics)\n5. Spec-driven development using OpenSpec for architecture decisions\n6. JWT-based security with cached JWKS validation\n7. Async-first design with async SQLAlchemy for performance\n\nThe service is in active development with focus on data consistency during migration and establishing the foundation for Temporal-based workflow orchestration.'}, {'type': 'text', 'text': "agentId: a055d6d (for resuming to continue this agent's work if needed)"}]
⏺ Now let me look at what the temporal workers currently call to understand the API contract needed.
⏺ Task(Explore temporal processors)
⎿ [{'type': 'text', 'text': 'Perfect! Now let me create a comprehensive summary document based on my exploration:\n\n## Temporal Workers' Requirements for Autoship Service\n\nBased on my exploration of the magento-temporal-processors codebase, here's a comprehensive summary of the autoship-related workflows and their requirements for the autoship service:\n\n### Overview\n\nThe temporal workers handle autoship/AROPE (Automatic Reorder Processing Engine) workflows through two main orchestration layers:\n\n1. MGAutoshipOrchestratorWorkflow - Master orchestrator that fetches all customers with schedules and spawns per-schedule child workflows in batches\n2. MGAutoshipScheduleProcessorWorkflow - Per-schedule worker that executes specific actions (prefill, AROPE, reminders)\n\n---\n\n### 1. CMS API Endpoints Required\n\nThe autoship service needs to implement or expose the following endpoints. All are configurable via environment variables:\n\n#### A. Fetch Endpoints\n\n| Endpoint | Default Path | Method | Purpose | Input |\n|----------|-------------|--------|---------|-------|\n| Fetch Customers with Schedules | /autoship/temporalCustomersWithSchedules | GET | Unified endpoint: fetch customers and their schedules with computed dates and readiness flags | limit (1-10000, default 100), offset (default 0), optional customer_id |\n| Fetch Schedules (Deprecated) | /autoship/temporalSchedules | GET | Legacy endpoint to fetch schedules for a customer | type (single_cart), is_prefilled (0 or 1), limit, optional customer_id |\n| Fetch Customers (Deprecated) | /autoship/temporalSingleCartCustomersToProcess | GET | Legacy endpoint to fetch customers ready for processing | limit |\n\n#### B. Action Endpoints\n\n| Endpoint | Default Path | Method | Purpose | Input | Output |\n|----------|-------------|--------|---------|-------|--------|\n| Assign Items to Cart (Prefill) | /autoship/assignItemsToMasterCart | POST | Add schedule items to customer's master cart | JSON: {"schedules": [schedule_ids]} | {success, schedule_succeeded: [], schedule_failed: []} |\n| Run AROPE | /autoship/runarope/{customer_id} | POST | Process autoship orders for a customer | Empty body {} | {success: bool, message: str, schedules: {success: [], failed: []} or []} |\n| Send T-1 Reminder | /autoship/schedule/{schedule_id}/send-one-day-reminder | POST | Send 1-day before delivery reminder | Empty body {} | {sent: bool, reason: str} |\n| Send T-3 Reminder | /autoship/schedule/{schedule_id}/send-three-days-reminder | POST | Send 3-days before delivery reminder | Empty body {} | {sent: bool, reason: str} |\n| Force Send T-1 Reminder | /autoship/schedule/{schedule_id}/force-send-one-day-reminder | POST | Force send T-1 reminder bypassing eligibility checks | Empty body {} | {sent: bool, reason: str} |\n| Force Send T-3 Reminder | /autoship/schedule/{schedule_id}/force-send-three-days-reminder | POST | Force send T-3 reminder bypassing eligibility checks | Empty body {} | {sent: bool, reason: str} |\n\n---\n\n### 2. Data Models & Response Structures\n\n#### Customer with Schedules Response\n\npython\n{\n "customers": [\n {\n "customer_id": int,\n "schedules": [\n {\n "schedule_id": int,\n "scheduled_at": str, # ISO date\n "t3_date_at": str, # ISO date (3 days before)\n "t1_date_at": str, # ISO date (1 day before)\n "status": int,\n "is_prefilled": bool,\n "is_order_soon_email_sent": int,\n # CMS Readiness Flags - CRITICAL for workflow logic\n "is_ready_for_prefill": bool,\n "is_ready_for_t1": bool,\n "is_ready_for_t3": bool,\n "is_ready_for_order_processing": bool\n }\n ]\n }\n ]\n}\n\n\n#### Critical: CMS Readiness Flags\n\nThe orchestrator workflow depends heavily on these flags to determine what actions to take:\n\n- is_ready_for_prefill - Controls whether to add items to customer's master cart\n- is_ready_for_t1 - Controls whether to send 1-day reminder\n- is_ready_for_t3 - Controls whether to send 3-day reminder\n- is_ready_for_order_processing - Controls whether to run AROPE (process orders)\n\nIf all flags are False for all schedules, the orchestrator logs a warning about potential CMS API issues.\n\n---\n\n### 3. Workflow Requirements & Feature Flags\n\nThe orchestrator accepts configuration flags that can be overridden per execution:\n\n| Flag | Default | Purpose | Config Env Var |\n|------|---------|---------|-----------------|\n| enable_prefill | true | Enable the prefill step (add items to cart) | AUTOSHIP_ENABLE_PREFILL |\n| enable_arope | true | Enable AROPE step (process orders) | AUTOSHIP_ENABLE_AROPE |\n| send_t1_reminder | true | Enable T-1 (1-day) reminder notifications | AUTOSHIP_SEND_T1_REMINDER |\n| send_t3_reminder | true | Enable T-3 (3-day) reminder notifications | AUTOSHIP_SEND_T3_REMINDER |\n| limit | 1000 | Max customers to fetch per run | AUTOSHIP_DEFAULT_LIMIT |\n| batch_size | 50 | Max child workflows per batch | AUTOSHIP_DEFAULT_BATCH_SIZE |\n| failure_rate_threshold | 0.5 | Alert if >50% of child workflows fail | AUTOSHIP_FAILURE_RATE_THRESHOLD |\n\n---\n\n### 4. Temporal Task Queues\n\nThe workers use dedicated task queues:\n\n- Autoship Task Queue: mg-autoship-queue (configurable via AUTOSHIP_TEMPORAL_TASK_QUEUE)\n- Workflow IDs: autoship-customer-{customer_id}-schedule-{schedule_id} (per-schedule child workflows)\n\n---\n\n### 5. Workflow Execution Order (Per Schedule)\n\n\nScheduleProcessorWorkflow:\n 1. Prefill (if enabled AND is_ready_for_prefill == true)\n 2. Send T-3 Reminder (if enabled AND is_ready_for_t3 == true)\n 3. Run AROPE (if enabled AND is_ready_for_order_processing == true AND first schedule for customer)\n 4. Send T-1 Reminder (if enabled AND is_ready_for_t1 == true)\n\n\nImportant: Only the first schedule per customer triggers AROPE to avoid duplicate processing.\n\n---\n\n### 6. Key Data Points Needed\n\nThe autoship service must provide:\n\n#### Per Customer\n- Customer ID\n- List of active autoship schedules\n\n#### Per Schedule\n- Schedule ID\n- Customer ID\n- Scheduled delivery date\n- T-3 and T-1 reminder dates (computed)\n- Whether schedule is already prefilled\n- Readiness flags (is_ready_for_prefill, is_ready_for_t1, is_ready_for_t3, is_ready_for_order_processing)\n- Schedule status\n\n#### For AROPE Processing\n- Which schedules need items added to cart\n- Customer's master cart state\n- Order processing eligibility per schedule\n\n#### For Reminders\n- Customer's current membership status (active/inactive)\n- Whether schedule is prefilled (for eligibility)\n- Whether reminder has already been sent\n- Customer contact information (email/SMS)\n\n---\n\n### 7. Metrics & Observability\n\nThe workers track the following metrics (via OpenTelemetry):\n\nOrchestrator Level:\n- autoship_orchestrator_runs_total - Total orchestrator executions\n- autoship_orchestrator_failure_total - Orchestrator failures\n- autoship_customers_processed_total - Total customers processed\n\nSchedule/Customer Level:\n- autoship_customers_success_total - Successful child workflows\n- autoship_customers_failure_total - Failed child workflows (per customer_id and error)\n\nActivity Level:\n- prefill_success_total, prefill_failed_total\n- arope_success_total, arope_failed_total\n- reminder_sent_total, reminder_not_sent_total (by type: t1, t3)\n- autoship_activity_failure_total (by activity type)\n\nSchedule Distribution:\n- autoship_schedules_per_customer - Histogram of schedules per customer\n\n---\n\n### 8. Configuration from Environment\n\npython\n# CMS API Base Configuration\nCMS_BASE_URL=<url>\nCMS_HOST=<host-header>\n\n# API Paths (all customizable)\nAUTOSHIP_PATH_FETCH_SCHEDULES=/autoship/temporalSchedules\nAUTOSHIP_PATH_FETCH_CUSTOMERS=/autoship/temporalSingleCartCustomersToProcess\nAUTOSHIP_PATH_FETCH_CUSTOMERS_WITH_SCHEDULES=/autoship/temporalCustomersWithSchedules\nAUTOSHIP_PATH_ASSIGN_TO_CART=/autoship/assignItemsToMasterCart\nAUTOSHIP_PATH_RUN_AROPE=/autoship/runarope\nAUTOSHIP_PATH_T1_REMINDER=/autoship/schedule\nAUTOSHIP_PATH_T3_REMINDER=/autoship/schedule\n\n# Feature Flags\nAUTOSHIP_ENABLE_PREFILL=true\nAUTOSHIP_ENABLE_AROPE=true\nAUTOSHIP_SEND_T1_REMINDER=true\nAUTOSHIP_SEND_T3_REMINDER=true\n\n# Batch Configuration\nAUTOSHIP_DEFAULT_LIMIT=1000\nAUTOSHIP_DEFAULT_BATCH_SIZE=50\n\n# Alerting\nAUTOSHIP_FAILURE_RATE_THRESHOLD=0.5\n\n# Temporal\nAUTOSHIP_TEMPORAL_TASK_QUEUE=mg-autoship-queue\nTEMPORAL_HOST=<temporal-host>\nTEMPORAL_NAMESPACE=default\n\n\n---\n\n### 9. Critical Implementation Notes\n\n1. Duplicate AROPE Prevention: Only the first schedule per customer should trigger AROPE. Subsequent schedules for the same customer should set run_arope=false.\n\n2. Readiness Flag Logic: The CMS API must return accurate readiness flags. If all flags are false across all schedules, the orchestrator logs warnings indicating a potential CMS issue.\n\n3. Batch Processing: The orchestrator processes schedules in configurable batches (default 50) to control concurrency and prevent resource exhaustion.\n\n4. Failure Tolerance: The orchestrator allows up to 50% (default) of child workflows to fail before logging errors. This is configurable.\n\n5. Host Header: The CMS API requires a custom Host header for routing (defaults to cms-api.stg.thrivemarketinternal.com).\n\n6. Activity Timeouts:\n - Short operations (fetch, send reminders): 10 seconds\n - Long operations (prefill, AROPE): 30 seconds (with retries)\n\n---\n\nThis comprehensive summary provides the autoship service with all requirements needed to support the temporal workflows.'}, {'type': 'text', 'text': "agentId: a8dbf1d (for resuming to continue this agent's work if needed)"}]
⏺ Now I have a complete picture. Let me create a comprehensive project plan based on all this information.
| Component | Status |
|---|---|
| autoship-service | Deployed, dual-read infrastructure, most APIs return 501 |
| Temporal workflows | In progress - replacing Airflow DAGs |
| Source of Truth | Magento database |
| Feature flags | autoship-data-source flag implemented |
Goal: All consumers can read autoship data from the service, while Magento remains source of truth for writes.
Epic: TM-509 - Implement client (user)-facing APIs
These are what mobile/web apps will call:
| Endpoint | Purpose | Priority |
|---|---|---|
GET /subscriptions |
List user's autoship subscriptions | P0 |
GET /subscriptions/{id} |
Get subscription details | P0 |
GET /subscriptions/{id}/items |
List items in subscription | P0 |
GET /subscriptions/{id}/schedules |
List upcoming schedules | P0 |
GET /schedules/{id} |
Get schedule details | P1 |
Dependencies:
- Frontend-Core to pass through JWT tokens (bypass Magento as auth gateway)
- Clients need JWT from Justin-Auth-Service
Epic: TM-381 - Migrate Autoship DAGs to Temporal
These are what the temporal workers need:
| Endpoint | Purpose | Current Owner |
|---|---|---|
GET /autoship/temporalCustomersWithSchedules |
Unified fetch for orchestrator | CMS API |
POST /autoship/assignItemsToMasterCart |
Prefill operation | CMS API |
POST /autoship/runarope/{customer_id} |
Process orders | CMS API |
POST /autoship/schedule/{id}/send-*-reminder |
Notifications | CMS API |
Strategy: Initially, temporal workers continue calling CMS API. These migrate later when autoship-service owns writes.
For backward compatibility during migration
| Endpoint | Purpose |
|---|---|
GET /admin/subscriptions |
List subscriptions (with filters) |
GET /admin/subscriptions/{customer_id} |
Get customer's subscriptions |
GET /admin/schedules |
List schedules (with date filters) |
Note: These should be "hope-to-not-support-forever" - documented as deprecated from day 1.
Approach: Direct DB access to autoship-service database (not Magento)
- Set up Fivetran connector to new database
- Run in parallel with Magento connector during migration
Goal: All read traffic goes through autoship-service (still reading from Magento via dual-read).
Blocked by: Phase 1.1 complete, Frontend-Core JWT passthrough
- Mobile apps update to call autoship-service APIs
- Web app updates to call autoship-service APIs
- Use feature flags for gradual rollout
- Monitor discrepancy logs (already implemented)
Epic: TM-391 - Migrate Prefill API from Magento to Autoship Service
- Point
AUTOSHIP_PATH_FETCH_CUSTOMERS_WITH_SCHEDULESto autoship-service - Prefill and AROPE continue calling CMS API (they do writes)
- Validate schedule data matches between sources
- Point admin UIs to autoship-service admin APIs
- Magento admin pages read from autoship-service
- Feature flag
autoship-data-source=bothenables dual-read - Monitor
autoship.datasource.discrepancy.countmetric - Fix any data inconsistencies found
Goal: Autoship-service becomes source of truth for writes.
| Endpoint | Purpose | Notes |
|---|---|---|
POST /subscriptions |
Create subscription | |
PUT /subscriptions/{id} |
Update subscription | |
DELETE /subscriptions/{id} |
Cancel subscription | Soft delete |
POST /subscriptions/{id}/items |
Add item | |
PUT /items/{id} |
Update item | |
DELETE /items/{id} |
Remove item | |
POST /schedules |
Create schedule | For Ship Now |
PUT /schedules/{id} |
Update schedule | Skip, reschedule |
Epic: TM-518 - Migrate Order Processing API to Autoship Service
| Endpoint | Purpose |
|---|---|
POST /schedules/{id}/prefill |
Add items to cart (calls Buy-and-Track) |
POST /schedules/{id}/process |
Process order (calls Buy-and-Track) |
External Dependencies:
- Buy-and-Track:
/cms-api/add-to-cart - Buy-and-Track:
/cms-api/checkout
Option A (Preferred from meeting notes): Big Bang Write Migration
- Coordinate downtime window
- Stop all write paths to Magento autoship tables
- Backfill any delta from Magento → autoship-service DB
- Enable writes to autoship-service
- Update all clients in coordinated release
Option B: Dual-Write (more complex)
- Implement dual-write to both DBs
- Gradually migrate clients
- Stop writing to Magento
- More complex, but less downtime
- Full historical backfill from Magento to autoship-service
- Validate counts and checksums
- Run reconciliation queries
- Set feature flag
autoship-data-source=autoship - Remove Magento database connection from autoship-service
- Remove dual-read code
- Remove
Thrive_Autoshipmodule from Magento - Drop Magento autoship tables (after data retention period)
From Phase 2 docs - long-lived workflow per user/schedule
- Migrate from batch orchestrator to individual workflows
- Each workflow waits for its ship date
- Better observability per customer
Need: Web UI for member services to view/edit customer subscriptions
Options:
- Build in autoship-service (React admin?)
- Build Magento admin that calls autoship-service APIs
- Use existing tooling (Retool, etc.)
Epic: TM-505 - Migrate Autoship Notifications to Autoship service
- T-1 and T-3 reminders currently in CMS API
- Migrate to Temporal workflows in autoship-service
- OpenSpec proposal exists:
add-temporal-notification-workflows
Epic: TM-573 - Change Prefill + Order Processing time to customer's local timezone
- Use shipping address to determine timezone
- Currently in progress
┌─────────────────────────────────────────────────────────────────┐
│ Phase 1: Read APIs │
├─────────────────────────────────────────────────────────────────┤
│ 1.1 Public APIs 1.2 Temporal APIs 1.3 Admin APIs │
│ ↓ ↓ ↓ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Phase 2: Migrate Readers │
├─────────────────────────────────────────────────────────────────┤
│ 2.1 Frontends ──────→ 2.2 Temporal ──────→ 2.3 Admin │
│ │ │ │ │
│ └─────────────────────┴────────────────────┘ │
│ ↓ │
│ 2.4 Data Validation │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Phase 3: Write Migration │
├─────────────────────────────────────────────────────────────────┤
│ BLOCKED BY: Buy-and-Track cart/checkout APIs │
│ │
│ 3.1 Write APIs → 3.2 Processing APIs → 3.3 Migration → 3.4 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Phase 4: Cleanup │
└─────────────────────────────────────────────────────────────────┘
| Team | Dependency | Phase |
|---|---|---|
| Frontend-Core | JWT passthrough (bypass Magento auth) | 1.1 |
| Buy-and-Track | /cms-api/add-to-cart API |
3.2 |
| Buy-and-Track | /cms-api/checkout API |
3.2 |
| Data | Fivetran connector to new DB | 1.4 |
| Member Services | Admin UI requirements | 1.3 |
-
Ship Now / Prefill Now handling - How do immediate actions affect subsequent schedules? Does Buy-and-Track need to tell Autoship "Ship Now happened"?
-
Admin UI - Build custom, extend Magento, or use Retool?
-
Migration downtime - How much is acceptable? Affects Option A vs B decision.
-
Data warehouse contract - What exactly does the data team need from the new schema?
Want me to:
- Create Jira tickets for any of these work items?
- Write an OpenSpec proposal for any specific phase?
- Dive deeper into any particular area?
Love this. Can you make this a markdown doc that clearly explains things to people less familiar? Include as many links as is helpful. Don't recommend extending magento's admin UI. Note that we need to write a doc specifically about how to handle the write transition.
⏺ I'll create a comprehensive markdown document that's accessible to people less familiar with the project.
⏺ Write(/Users/justinabrahms/src/github.com/thrivemarket/autoship-service/docs/migration-plan.md) ⎿ File created successfully at: /Users/justinabrahms/src/github.com/thrivemarket/autoship-service/docs/migration-plan.md
⏺ Now let me create a stub document for the write transition that we called out needs its own detailed design.
⏺ Write(/Users/justinabrahms/src/github.com/thrivemarket/autoship-service/docs/write-transition-design.md) ⎿ File created successfully at: /Users/justinabrahms/src/github.com/thrivemarket/autoship-service/docs/write-transition-design.md
⏺ Created two documents:
-
docs/migration-plan.md - The main migration plan with:
- Executive summary and background for newcomers
- Current vs target state diagrams
- Four migration phases with details
- Links to all relevant Jira epics and Confluence pages
- External dependencies table
- Open questions (answered and open)
-
docs/write-transition-design.md - A stub/template for the Phase 3 write transition that includes:
- Outline of sections that need to be written
- The three cutover strategy options to evaluate
- Rollback planning requirements
- Runbook structure
- Open questions to answer before proceeding
Key things I called out in the docs:
- Admin UI: Only Retool or build-from-scratch options (no Magento extension)
- Write transition needs its own detailed doc before Phase 3
- The team alignment meeting decisions are captured (API integration, clean cutover preferred)
- All the Jira epic links and Confluence page links are included
Want me to commit these and push them up?
open docs/migration-plan.md
publish-session /publish-session
⏺ I'll commit these documents and push them up.
⏺ Bash(git add docs/migration-plan.md docs/write-transition-design.md && git status) ⎿ On branch add-data-source-feature-flag Your branch is up to date with 'origin/add-data-source-feature-flag'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: docs/migration-plan.md
new file: docs/write-transition-design.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
.ocr/
.qlty/logs
AGENTS.md
CLAUDE.md
TODO.md
openspec/AGENTS.md
openspec/changes/add-temporal-notification-workflows/
scripts/e2e-test.sh
scripts/get-staging-token.sh
⏺ Bash(git commit -m "$(cat <<'EOF' docs: Add comprehensive migration plan and write...) ⎿ Exit code 128 │ No config files with names ["lefthook" ".lefthook" ".config/lefthook"] have been found in "/Users/justinabrahms/src/github.com/thrivemarket/autoship-service" │ No config files with names ["lefthook" ".lefthook" ".config/lefthook"] have been found in "/Users/justinabrahms/src/github.com/thrivemarket/autoship-service" error: 1Password: agent returned an error
fatal: failed to write commit object
publish-session /publish-session
⏺ Skill(...) ⎿ Launching skill: share
⏺ Bash(~/.claude/scripts/share-session.sh "$(pwd)")