# STAMP-3 — #132's pre-open→previous-day rule inverts for current-state captures (shortability), and three stamp sites still judge the UTC date

status: open · raised by the monthly research audit, G1 fresh-eyes audit of PR #132 (2026-09-03, `research/RESEARCH_AUDIT_LOG.md` run 1) · class: moat / loudness · judgement: **YES** (MAJOR lead + three MINOR riders, same class) · effort ~0.25 pd · horizon: live now — the trigger window is the same displaced-cron regime that fired STAMP-1 three times

---

**Plain-language summary for an owner reading one paragraph.** The STAMP fix
(#132) stamps every capture with the market session it observed, and its rule
says a pre-open instant belongs to *yesterday* — right for option chains
(pre-open, the only chain data that exists is yesterday's close), but
backwards for the shortability snapshot, which photographs *right now*:
Alpaca's current ETB/HTB borrow flags. If the 19:00Z skew workflow is ever
delayed into the next morning's pre-open window (observed dispatch delays
already reach 9 h; 9 h is exactly midnight ET), the run would write
*this morning's* borrow flags into *yesterday's* file, permanently — and the
TIME-GATED flip panel would date an overnight ETB→HTB flip to the wrong
session. Before #132 this one stream stamped the current UTC day, which was
content-correct; the fix made it wrong. Three smaller leftovers of the same
class ride along below.

*Why this is a new row, not STAMP-1/2 relitigation (C0).* STAMP-1/2 are
BUILT and their criteria verified met (`queue/built/`, audit run 1 M3). This
row records a defect their fix *introduced* (the pre-open rule applied to a
current-state stream — a case neither row's registration contemplated) plus
sites the fix never reached. Nothing here reopens their verdicts.

## Mechanism — code-verified at HEAD (audit run 1)

**Lead (MAJOR).** `utils/calendar.py:66-79`: `market_session_date` maps any
pre-09:30-ET instant to `previous_trading_day`. `cli.py:2157` passes that
session into `capture_shortability(as_of=...)` — directly under the comment
"the snapshot's `as_of` IS the present by construction — a threaded
historical date would FABRICATE state this current-state-only API cannot
serve" (`cli.py:2153-2155`). `borrow_flags.py` then keys the parquet
filename and the idempotency skip (`if f.exists(): return`) on that date.
Failing scenario: any firing of `snapshot-shortability` in the 04:00–13:30Z
window — a 9h-delayed 19:00Z cron, or a morning manual re-dispatch after a
missed day — resolves to yesterday, yesterday's file is missing (the delayed
run *was* yesterday's), and morning-refreshed flags are written to
`data/shortability/<yesterday>.parquet` with nothing to flag the fabrication.
No test covers a pre-open instant reaching this writer.

**Riders (MINOR, same class — residual UTC judges/stamps after #132):**
1. `market_session_date`'s docstring promises "a holiday stays the holiday",
   but the pre-open branch on a holiday jumps two calendar days back
   (`2026-09-07T12:00Z` Labor Day 08:00 ET → `2026-09-04`); the pinned
   holiday test probes only 14:35Z. Behavior may even be desirable
   (pre-open holiday catch-up) — but it contradicts the documented design
   and is unpinned either way.
2. The per-sleeve digest's no-run canary still judges the UTC date
   (`execution/digest.py:49` via `cli.py:4378`, `today=None` →
   `date.today()`): the backwards fire #132 fixed in the fleet path
   survives in the manual single-sleeve path, and the retained
   `test_clock_hermeticity` entry explicitly sanctions the read.
3. Failure-alert email subjects stamp `date.today()` (UTC) (`cli.py:3628`,
   `:4392`): a post-midnight displaced failure emails tomorrow's date in
   the channel a human reads to decide which session to investigate.
   (Hygiene, fold into the same PR: duplicate helper import at
   `cli.py:4713`/`:4760`; `export-public`'s `date.today()` feeds a dead
   parameter.)

## Data plan

Owned and free — no new data. The fix is stamp/judge logic only; the
existing committed capture instants (the 20:14–20:43Z band plus the
incident instants already pinned in `tests/test_utils/
test_market_session_date.py`) are the replay corpus.

## Test design / fix shape (propose-only; owner decides)

For the lead: the shortability writer must not inherit the chains rule. The
design choice to make explicit — REFUSE pre-open (exit non-zero naming both
dates; a pre-open borrow snapshot is neither yesterday's close state nor the
day's regular capture) or STAMP-CURRENT-ET-DAY (content-correct, degraded
basis, logged loudly). Either is defensible; silently writing yesterday is
not. Riders: route the per-sleeve digest and the alert subjects through
`market_session_date()`; pin the pre-open-holiday branch to whatever the
docstring is amended to say.

## Kill criterion — pre-registered, negative-controlled in both directions

On a temp copy with the clock frozen at a pre-open instant
(`2026-09-04T12:00:00Z`, 08:00 ET): `thales snapshot-shortability` must
never produce a file dated the previous trading day — it refuses naming
both dates, or writes the current ET day per the chosen design. **Negative
control:** replay the committed capture band (20:14–20:43Z normal days, the
09-01 post-close instant, and the pinned incident instants) and assert
byte-identical stamping behavior vs HEAD — the guard fires on none of them;
a same-session re-run still takes the idempotent skip. If the guard fires
on any normally-timed run, revert it and record the miss rather than tuning
the threshold after the data exists. Riders red-first: the per-sleeve
digest judged at a frozen post-UTC-midnight instant must name the observed
session (red before, green after); the holiday pre-open branch gets one
pinned test matching the amended docstring.

*Effort* ~0.25 pd (one writer guard + two one-line reroutes + three tests).
*Horizon:* decidable immediately; the exposure window is the standing
displaced-cron regime, outage-correlated exactly as STAMP-1/2 were.

---

## BUILT — 2026-09-03 (hotfix of the same-day regression, hours after the row was raised)

**Route — recorded honestly.** This row records a defect that PR #132
(merged 2026-09-03 02:02Z) INTRODUCED on a live capture stream; it was fixed
as a hotfix in the same interactive session that built #132, on the owner's
standing "let's do it" for the post-audit list — not via `queue/approved/`.
A same-day regression of a just-merged change is hotfix territory rather
than research-queue territory, but the research audit's counter stands as
written: the approved route has still never been used (0-for-7 counting this).

**Design choice made explicit (the row asked for one): the shortability
writer SKIPS pre-open** (nothing written, loud yellow line, exit 0) rather
than exiting non-zero — a pre-open borrow snapshot is neither yesterday's
close state nor today's regular capture, and the in-session dispatch
(12:00 PT) captures the day; a red exit here would page on every harmlessly
delayed backup cron. In session and post-close it stamps the **ET calendar
date**, never the previous session. New helper `utils.calendar.session_phase`
carries the rule; `market_session_date`'s docstring now says plainly it is
NOT for current-state captures.

**Riders folded in:** the per-sleeve digest's `gather_digest` default and the
two failure-alert subjects use the market session; `export-public`'s `today`
too; the four local `market_session_date` imports in `cli.py` collapsed to one
module import; the pre-open-holiday branch is pinned to what the code does
(08:00 ET on Labor Day → the previous trading day) and the docstring amended
to match.

**Kill criterion — met** (`tests/test_utils/test_market_session_date.py`):
`session_phase` classifies the incident instants (04:00Z = midnight ET is the
NEXT date, pre-open); the CLI test proves the writer is never reached
pre-open and receives the ET date in session; the UTC-judge lint asserts the
riders are gone. Full suite 1357 passed; trading gate green under all three
sleeve environments.

---

*Provenance recorded by triage 2026-09-07 (annotation only; the record above is untouched): **correcting a misleading reference in the built section above** — it names PR #132 as this row's PR, but #132 is the merge that INTRODUCED the pre-open rule this row corrects. STAMP-3 was fixed by the hotfix **PR #147** (`99656aa`, merged 2026-09-03); the row was raised in **PR #144**.*
