Architecture
Array Link Writes
The relinker worker and the POST /{user_id}/processor endpoint share one implementation in
pkg/arraylink, and both hold an entitlement row across two network calls: resolving the
user’s primary account at transactions-service, then posting the processor token to Array.
Those writes are whole-item puts, so they carry back exactly what was read and nothing else.
A row that stopped being the active one during that window — retired in favour of a newer
Array account, or removed by cleanup — must not be written back: the put would return it to
Active and drop the ttl that schedules the old Array account for deletion, leaving that
account with no cleanup path and the user with two active rows.
Both writes therefore go through dynamo.SaveIfActive, which requires the row to still
exist and still be Active. A rejected write surfaces as arraylink.ErrEntitlementNotActive
and is terminal on both paths: the worker records it and does not retry, and the endpoint
answers 404. Array keeps the link either way — what has changed is which row owns it, and
re-posting cannot make a retired row current again.
Signup is not affected. It builds a fresh row and owns a single unconditional write, so it has nothing stale to overwrite.
Array Account Cleanup
Deleting a live Array account is unrecoverable, so a cleanup record — never the entitlement row — carries the TTL that triggers one. Two things schedule one:
-
A cancellation, with a 63-day grace period (
CLEANUP_TTL_DAYS), on a single key per user so that reactivating cancels it. Deleting that record is the only delete this service performs. -
A second Array signup, scheduling the account the new row supersedes with a much shorter
STALE_ENTITLEMENT_TTL_DAYS, default 1 day, on a key ending in the Array account id. Sitting off the cancellation key is what stops anything cancelling it: that account exists because the system made a second one, not because the user chose anything.
DynamoDB removing the expired record is the trigger, and the only row it ever takes.
expiration-handler then deletes the Array.io account the record names — not whatever account
the user holds now — and marks the entitlement row that named it Removed, found through
GSI1. Entitlement rows are never deleted.
That last step is best effort. The record is already gone by the time the handler runs, so if the GSI1 lookup fails the account has still been deleted at Array, the row keeps its old status, and nothing remains to trigger another attempt.
No in-use check is needed: an account is only scheduled because a different one replaced it, and the same account arriving again returns at request time, so a record can never name the account in use.
A 404 from Array means the account is already gone, which is the outcome cleanup wanted, so
it is reported as ErrArrayUserAlreadyDeleted and treated as success. The record that fired
the event is already gone, so returning an error instead redelivers off the stream until it
ages out, every attempt hitting the same 404.