| 113 | * fails — a storage error is logged and the caller continues. |
| 114 | */ |
| 115 | export const touchSubject = (db: FumaDb<any>, input: TouchSubjectInput): Effect.Effect<void> => |
| 116 | Effect.gen(function* () { |
| 117 | const externalId = input.externalId; |
| 118 | // A pure-org executor has no principal to record. |
| 119 | if (externalId == null) return; |
| 120 | |
| 121 | const throttleMs = input.lastSeenThrottleMs ?? DEFAULT_SUBJECT_LAST_SEEN_THROTTLE_MS; |
| 122 | const cacheKey = touchCacheKey(input.tenant, externalId); |
| 123 | const seenAt = lastTouchedAt.get(cacheKey); |
| 124 | // Already filed a sighting for this principal inside the window: the row |
| 125 | // exists and its `last_seen_at` is fresh enough, so there is nothing a |
| 126 | // query could change. THE hot path — skip the read entirely. |
| 127 | if (seenAt !== undefined && Date.now() - seenAt < throttleMs) return; |
| 128 | |
| 129 | // Bind the tenant policy context. `subject` is inert for this table (it is |
| 130 | // tenant-scoped, not owner-scoped) but the context shape is shared, and at |
| 131 | // both call sites the bound subject IS this external id. |
| 132 | const fuma = makeFumaClient( |
| 133 | withQueryContext(db, { |
| 134 | tenant: input.tenant, |
| 135 | subject: externalId, |
| 136 | } satisfies ExecutorOwnerPolicyContext), |
| 137 | ); |
| 138 | // No `tenant` clause: the tenant policy adds it to every read/update. |
| 139 | const where = (b: AnyCb): Condition | boolean => b("external_id", "=", externalId); |
| 140 | const now = Date.now(); |
| 141 | |
| 142 | const existing = yield* fuma.use("subject.findFirst", (query) => |
| 143 | asLooseSubjectDb(query).findFirst("subject", { where }), |
| 144 | ); |
| 145 | |
| 146 | if (!existing) { |
| 147 | yield* fuma |
| 148 | .use("subject.create", (query) => |
| 149 | asLooseSubjectDb(query).create("subject", { |
| 150 | tenant: input.tenant, |
| 151 | external_id: externalId, |
| 152 | created_at: new Date(now), |
| 153 | last_seen_at: now, |
| 154 | status: null, |
| 155 | }), |
| 156 | ) |
| 157 | .pipe( |
| 158 | // A concurrent first sighting of the same principal wins the unique |
| 159 | // index. That IS the row this call wanted, so the loser succeeds. |
| 160 | Effect.catchTag("UniqueViolationError", () => Effect.void), |
| 161 | ); |
| 162 | rememberTouch(cacheKey, now); |
| 163 | return; |
| 164 | } |
| 165 | |
| 166 | const lastSeenAt = existing["last_seen_at"]; |
| 167 | // bigint on drivers that return one (matches `tools_synced_at`'s read). |
| 168 | const lastSeenMs = lastSeenAt == null ? null : Number(lastSeenAt); |
| 169 | if (lastSeenMs !== null && now - lastSeenMs < throttleMs) { |
| 170 | // The persisted value is fresh, so the next sighting inside the window |
| 171 | // can skip this read too. Anchored to the PERSISTED timestamp, not to |
| 172 | // now, so the memory expires no later than the row does. |