This is a very typical Journey Builder / Attribute issue.
You have a 1:n relationship, one "person" has many "date" records.
Journey Builder has just one way to deal with 1:n relationships, it applies a "at least one record matches the criteria" logic. That looks exactly what it's doing in your case, as you rightly interpreted. "at least one date is 3 days before trial end date" evaluates to true.
I would introduce a logging mechanism: every combination of user and timestamp that has passed the journey once is upserted into a log data extension where (let's start simple) they get a field "processed" set to "true".
Now you can check if people have passed before and exclude them.
Either you make that a part of an SQL preprocessing logic (SELECT only subscriber & date combinations that are not in the log with "processed" != "true" next to them), which I would recommend, or, if you connect that DE to the contact model, you can also use a decision split (based on Contact Data
) that only lets contacts / dates who are NOT in the DE with a prior timestamp and "processed = true" follow the path to your wait decision.
Some food for thought on such a log:
This is generally pretty useful, so it can be worth of a generic setup so you can reuse this DE for many journeys.
If you also log the journey name and possibly path and event next to your subscriber, one log can serve many journeys. If you incorporate a timestamp instead of "true", or in addition to it, it gives you control also to clean up your logs after log entries reach a certain age, allowing reentry after a period of time has passed. If you also have the journey name in the log entry, the cleanup logic can be different for each journey, while still having only one DE. So you only have to connect this single DE to the Contact Model, not one for each journey.