Skip to content

Corporate-action guard for overnight paper holds (audit gap #8) - #222

Merged
seathatflowsinourveins merged 12 commits into
mainfrom
claude/paper-corporate-action-guard-20260924
Sep 25, 2026
Merged

seathatflowsinourveins merged 12 commits into
mainfrom
claude/paper-corporate-action-guard-20260924

Conversation

@seathatflowsinourveins

Copy link
Copy Markdown
Owner

What

This adds a corporate-action guard for adaptive paper runs that hold positions overnight (trading-lane audit gap #8). It is wired in only when session_policy.overnight_holds is enabled, and no shipped config enables that today. For each held, targeted or pending-buy symbol it does the following:

  • It blocks new entries, and cancels any resting buy, when a split, dividend, merger, spin-off or other listed action is effective between today and the next session (weekend and holiday dates included).
  • It flattens holdings that carry a confirmed action in that range.
  • It refuses the overnight hold, marking the run needs_attention so recovery takes over, when any held symbol could not be verified clear.

Source: Alpaca GET /v1/corporate-actions, called through the public alpaca-py get() with our own next_page_token pagination and a page ceiling.

  • The request sends region=us and data_quality=all, which includes incomplete records.
  • Window. Alpaca filters by process date, and for a cash dividend that is the payable date. So the guard fetches from anchor−7 to next session+90 days and filters locally on each action's governing date.
  • Unknown actions. Unmapped buckets, rows with no symbol and undated records all count as uncertainty, not as clear. Uncertainty is tracked separately from valid records, so a qualifying record still flattens.
  • Row cap. A result at the raw row cap across all pages fails closed.

Runtime.

  • Each fetch runs in a daemon thread, so a hung fetch cannot hold the account lock or delay exit.
  • Attempts use generations allocated before the thread starts. A late or cancelled attempt can never overwrite a newer result.
  • A timeout counts as a failure and is retried after 60 s. Cancellation only invalidates the attempt.
  • The final guard summary is recomputed from current holdings and monitor state right before the hold decision.
  • The 2027 NYSE calendar is added. It is cited to the NYSE holiday page and cross-checked against exchange_calendars 4.13.2.

Native read-only check (corporate-actions-native-check-20260924.json, market data only, no orders):

  • The original narrow window missed AAPL's 2024-08-12 dividend. The ex-date was 2024-08-12 and the process date equalled the payable date, 2024-08-15, a 3-day lag.
  • The wide window recovers it.
  • NVDA's 2024-06-10 forward split is observed.

Review

Six rounds.

  • Codex (gpt-6-astra) reviewed rounds 1–4 read-only. It found the process-date window miss (critical), the blocking fetch on the event loop, erasure of good records, overlapping workers, SDK silent drops, the data_quality default, a test that was really calling Alpaca, and more.
  • Claude reviewers probed each round, including runs with the network blocked. They built mutation harnesses, and the last one killed 66 of 67 mutants.

Codex limit. The Codex CLI hit its usage limit (it resets 2026-09-30 23:50), so rounds 5–6 had only the independent Claude review. Its final verdict at 65ee7fb was ready. The one surviving mutant is an untested worker-exception branch that only runs if the monitor itself raises.

Checks (merged tree, a3b9a23, network blocked)

Not covered

  • The 90-day forward pad is an assumption. It rests on three observed lags (3, 6 and 15 days) and is not a proven maximum. Alpaca does not guarantee that announcements are available, so an empty response cannot prove an action is absent.
  • No live overnight trial has exercised the guard. No shipped config enables overnight holds. Its evidence is local integration (the real run_native wiring with fake ports) plus the one native read-only source check.

🤖 Generated with Claude Code

Scout and others added 11 commits September 24, 2026 13:35
held_overnight trials could hold a position across a session boundary
without any adjustment for a split, reverse split, cash/stock dividend,
spin-off, merger or name change landing in that window. corporate_actions.py
adds a fail-closed guard: entries are blocked and an existing position is
force-flattened (ranked above the D5 gap-risk stop, below force_exit/STOP)
for any symbol whose corporate action falls inside the possible overnight
hold horizon, or whose Alpaca CorporateActionsClient lookup failed or was
ambiguous. Wired into native_strategy.AdaptiveStrategy.rebalance() and
runner.py's paper trial loop via the existing --env-file credential path;
decisions are recorded in the paper/live event stream without values.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…split, AAPL dividend, TSLA none)

AlpacaCorporateActionsSource.fetch returned NVDA forward_split 2024-06-10 and AAPL
cash_dividend 2024-08-12 (both the correct ex-dates) and nothing for TSLA in its window,
using this host's dedicated paper keys through runner.credentials. Read-only; no orders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…review findings

Merges origin/main (which carried #198's order-contract boundary and its
own runner.py/source-hashes.json changes) into the paper-lane
corporate-action guard branch, resolving the runner.py/source-hashes.json/
manifests/evidence.json conflicts, and folds in the independent review's
fix round for the guard itself:

- CRITICAL (1): widen the corporate-actions fetch window to anchor-7d..
  anchor+90d (Alpaca filters by process/payable date, not the ex_date this
  guard checks) and filter locally in evaluate_guard; treat a result at the
  page/limit cap as ambiguous; native re-check recorded in
  corporate-actions-native-check-20260924.json.
- HIGH (2): run the corporate-action refresh off the asyncio event loop in
  a worker thread, bounded by wait_for and an in-flight flag; pin a request
  timeout and cut retries on the underlying alpaca-py client; do the first
  fetch before run_native's tick loop starts.
- HIGH (3): a failed/stale refresh no longer overwrites a previously
  confirmed action with "lookup failed" -- the last good result is kept,
  with needs_attention added, so a confirmed flatten is never cancelled by
  a later failure.
- MEDIUM (4): evaluate_guard's date check is now an inclusive
  today<=action_date<=next_session_date range, catching a weekend/holiday
  governing date.
- MEDIUM (5): the run-outcome corporate-action guard summary
  (pending_must_flatten) is read by _honest_overnight_hold, so a symbol
  still flagged must_flatten at the run boundary forces needs_attention/
  recovery instead of a false held_overnight.
- MEDIUM (6): added the 2027 NYSE holiday/early-close calendar to
  sessions.py; a session-calendar failure now blocks-and-flags only, never
  force-flattens every holding.
- MEDIUM (7): fixed silent pass-throughs -- an omitted symbol defaults to
  lookup-failed; the refresh throttle is now keyed on the fetch window too;
  an unmapped corporate-action type fails closed instead of being dropped;
  unit-split alternate_symbol is mapped.
- MEDIUM (8): the guard is wired into the strategy only when
  overnight_holds is enabled; failures retry on a short ~60s interval
  instead of the full refresh interval; a guard summary is added to the
  run outcome.
- LOW (9): a symbol flagged by both the corporate-action guard and the
  gap-risk stop now gets exactly one sell action.
- LOW (10): a resting buy order for a symbol newly blocked this tick is
  cancelled.
- NIT (11): the decision event only carries the corporate_action_* keys
  when the guard is actually wired in (restores the byte-identical-when-off
  claim); a guard event is only re-emitted when the decision changes.

Adds targeted tests for each finding (corporate_actions.py's
CorporateActionMonitor/evaluate_guard/AlpacaCorporateActionsSource,
native_strategy.py's rebalance() wiring, and runner-level tests of the
fetch window, wiring decision, and threaded/bounded refresh scheduling).
Recomputed blueprints/us-equities/adaptive-paper/source-hashes.json and the
matching manifests/evidence.json entries programmatically from the changed
files' actual on-disk content.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nge test hermetic

- sessions.py: HOLIDAYS_2027/EARLY_CLOSES_2027 now cite the NYSE hours-calendars
  page (fetched 2026-09-24; lists 2026-2028) and the same-day exchange_calendars
  4.13.2 XNYS cross-check, which agrees exactly (10 weekday closures, early close
  2027-11-26). exchange_calendars_agrees(year) generalizes the 2026 check with
  padded bounds (Jan 1 is not a session).
- tests: add the 2027 agreement test; test_year_outside_calendar_refused now
  disables the exchange_calendars fallback, since CI installs that package and
  the test previously passed only where it was absent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…guard branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both independent reviews (Claude/rev-ca2 and Codex) found round 2 "not
ready". Addresses every item:

- HIGH (1): overlapping refreshes could let an older, slower attempt
  overwrite a newer confirmed action. CorporateActionMonitor now tracks a
  generation counter under a lock; refresh_timed_out() (called on a
  wait_for timeout) bumps it and treats the timeout as an immediate
  failure (short retry_seconds interval); _apply_success/_apply_failure
  drop any write from a superseded generation. The preflight fetch and
  tick-loop refresh now share one ca_refresh_state dict (in_flight +
  tasks), and run_native's shutdown path (_shutdown_corporate_action_tasks)
  cancels/joins every still-pending refresh task.
- HIGH (2): the SDK's CorporateActionsSet parser (13 branches, no
  else/default) silently dropped reorganization/partial_call/capital_
  gains_distribution buckets before this wrapper ever saw them, evading
  both unknown-type rejection and the cap check. AlpacaCorporateActionsSource
  now constructs with raw_data=True and parses the raw response directly
  via the private _get_marketdata, so every bucket (mapped or not) reaches
  this wrapper's own fail-closed logic. New tests fake the HTTP transport
  (not a pre-parsed response) so the real SDK code path runs.
- HIGH (3): the request now asks data_quality=all (the documented default,
  complete, excludes incomplete records); a record missing its governing
  date marks only its own symbol(s) LOOKUP_AMBIGUOUS, not the whole fetch.
- MEDIUM (4): the guard's summary now tracks pending_needs_attention_held
  (every currently-held symbol not positively verified clear);
  _honest_overnight_hold refuses held_overnight while it is non-empty, not
  just while pending_must_flatten is non-empty.
- MEDIUM (5): the guard now evaluates every symbol with a still-resting
  pending BUY order, not just decision.targets.keys(), so a corporate
  action discovered after a signal disappears still cancels that resting
  buy. The fake guard test fixture now filters to the requested
  held|candidate set instead of echoing every scripted decision.
- MEDIUM (6): the 90-day forward pad is now explicitly documented as an
  evidence-backed assumption (3 recorded lag observations, sourced), not a
  proven bound.
- LOW (7): a symbol omitted from an otherwise-successful fetch is now
  degraded (last good record preserved), never overwritten with
  LOOKUP_FAILED.
- LOW (8): corporate-actions-native-check-20260924.json is corrected
  (the 2024 window is explicitly labeled a manual approximation since
  sessions.py has no 2024 calendar; the real _corporate_action_fetch_window
  output is recorded separately; the AAPL dividend's lag is corrected to
  the measured 3 days, replacing the unmeasured "1-3 weeks" claim) and
  registered in manifests/evidence.json.
- NIT (9): the "bounded fetch"/pagination comments are corrected (a
  requests timeout bounds each read, not the whole paginated call; the SDK
  does paginate); a symbol that was previously flagged and resolves now
  emits an explicit "cleared" event.
- NIT (10): the session test's stale "CI installs that package" comment is
  corrected.

Re-ran the review's own mutate_rev.py unmodified (just retargeted): all 24
mutations now kill (22 directly; F2b/F5a/F5b's exact pre-fix text no
longer exists after this round's refactor into named, independently
testable functions -- their equivalents are covered by this round's own
mutation probe, also all killed). Added a matching native re-check
(raw-record lag query, data_quality=all) using the same credential-loading
recipe as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… found

Both independent reviews confirmed round 3 mostly holds (all 24 earlier
mutations still killed) but independently found the same new regression:

- HIGH: ambiguity erased a confirmed action. AlpacaCorporateActionsSource.
  fetch() used to overwrite a symbol's whole record list with
  LOOKUP_AMBIGUOUS whenever ANY row for that symbol lacked a governing
  date, discarding valid records collected in the SAME response (e.g. a
  confirmed split plus an undated dividend for the same symbol).
  CorporateActionMonitor._apply_success also overwrote a previously
  confirmed record with a later per-symbol LOOKUP_AMBIGUOUS/LOOKUP_FAILED
  result. Fixed at both layers: fetch() only ever falls back to the
  sentinel for a symbol with ZERO valid records of its own in that
  response; _apply_success degrades (keeping the last good record)
  instead of overwriting whenever the new per-symbol result is a
  sentinel.
- MEDIUM: raw rows with no identifiable symbol used to silently count as
  clear. A row (mapped or unmapped bucket) naming no symbol at all now
  marks every requested symbol ambiguous.
- MEDIUM: uncertainty discovered after the final rebalance() tick could
  still end held_overnight. The outcome's corporate_action_guard summary
  is now recomputed fresh (_final_corporate_action_guard_summary) against
  the guard's current evaluate() and the truly final held positions,
  immediately before the outcome is built, instead of trusting the
  strategy's last-tick snapshot.
- LOW: shutdown errors could bypass refresh-task cleanup -- session.stop()/
  port.stop()/the task wait are now in their own nested try, with
  _shutdown_corporate_action_tasks in its own guaranteed finally.
- LOW: the private _get_marketdata call is replaced with the public
  client.get() and this wrapper's own explicit next_page_token pagination
  (matching blueprints/us-equities/alpaca-historical's own bridge pattern
  for this endpoint), with region=us and a page-count ceiling that fails
  closed if a continuation token survives it.
- LOW: one unmapped-type row used to fail the fetch for every requested
  symbol. An unmapped bucket now degrades only the symbol(s) its own rows
  actually name (Alpaca API reference, corporateactions-1, fetched
  2026-09-24: partial_call/reorganization/capital_gains_distribution
  documented as additional types; data_quality accepts complete/all).
- LOW (measured): a hung refresh thread used to delay run_native's own
  result-save/account-lock release by the hang's full duration, because
  asyncio.run()'s cleanup joins every asyncio.to_thread/default-executor
  thread. The worker now runs on a daemon thread bridged back via
  loop.call_soon_threadsafe; asyncio.run() never waits for it (measured:
  a 4s hang delayed exit by ~4s before, ~0.1s after).
- NIT: a timeout processed immediately after a successful commit for the
  same generation is now ignored (a new _committed_generation check),
  instead of degrading an already-fresh result for the retry interval.
- Wording: corrected the "never overlapped"/"bounded fetch" claims to
  state exactly what actually bounds each thing, and the native-check
  record's "2026 is the only calendar year" claim (2027 is also covered).

Adds targeted tests for every finding above, including tests that go
through the real HTTP-mocked SDK pagination path (a two-page response
with a real next_page_token, and the cap enforced across pages) and a
real run_native drive proving the shutdown path actually cancels a
hanging task and invalidates it via refresh_timed_out.

Re-ran the review's own mutate_rev3.py (retargeted, unmodified
mutations): the two mutations flagged as regressions this round (G9, G10)
and G5 are now killed. G6 ("run_native never shuts down refresh tasks")
still survives -- verified redundant, not a real gap: run_native is only
ever invoked via asyncio.run() (both directly and through main()'s own
asyncio.run(execute())), and asyncio.run()'s own Runner.close() calls
_cancel_all_tasks() before shutting down the loop, cancelling every
outstanding task regardless of this module's own explicit cleanup. The
explicit _shutdown_corporate_action_tasks call remains valuable for the
exception-path case (finding, LOW #7, now fixed via the nested finally)
and for a hypothetical caller that reuses an already-running loop, not for
this survivor's literal scenario.

Recomputed source-hashes.json and the matching manifests/evidence.json
entries programmatically from the changed files' actual on-disk content;
registered the native-check record's own row updates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…cancellation race, fix the network-leaking pagination test

Both independent reviews converged on one regression: fetch() collapsed
per-symbol uncertainty into a LOOKUP_AMBIGUOUS sentinel that only applied
when a symbol had zero valid records, so a benchmark symbol's routine
dated dividend (SPY/QQQ/IWM/DIA almost always have one in the 97-day
window) silently hid an unrelated undated/unmapped/symbol-less row for
that same symbol. AlpacaCorporateActionsSource.fetch() now returns
(records, ambiguous_symbols) as two independent values; CorporateAction
Monitor.complete_attempt keeps the records and separately flags the
symbol degraded when it is also ambiguous, so a confirmed action still
flattens while residual uncertainty still raises needs_attention.

Codex additionally found a genuine cancellation race: the monitor's
generation was allocated lazily inside refresh() itself, which only ran
once a launched worker thread actually started executing -- a thread
still merely queued by the OS at shutdown time was invisible to the
generation-based invalidation check, letting a delayed worker commit a
stale result after shutdown. CorporateActionMonitor now exposes two-
phase begin_attempt/run_attempt/fail_attempt/invalidate_attempt/
refresh_timed_out primitives; the generation is allocated on the calling
coroutine before the worker thread is even created. Cancellation now
calls the non-degrading invalidate_attempt instead of the degrading
refresh_timed_out, so a routine in-flight refresh cancelled at shutdown
no longer forces an otherwise-clean cached result into needs_attention.

Both reviews also found the page-ceiling pagination test constructed the
real AlpacaCorporateActionsSource before entering its own HTTP patch
context, so it silently issued ~58 real requests to Alpaca's servers and
passed on the resulting 401s without ever exercising its own scripted
fixture; the source is now built inside the patch, the exact page count
served is asserted, and both corporate-action test modules gained a
module-level real-network guard.

Also: an empty-string next_page_token no longer counts as a
continuation; Thread.start() now sits inside the try/finally that resets
in_flight; the "prevent overlap" comment was corrected to "reject stale
writes"; the shutdown/final-summary/hold tests were strengthened so they
fail on the exact regressions they target rather than passing on
asyncio.run()'s own unrelated auto-cancellation or an in-memory stale
snapshot. New tests cover region=us/data_quality=all on every page, the
worker thread being a daemon, in_flight recovering from a Thread.start()
failure, and a run_native-driven end-to-end refusal of held_overnight
when a refresh degrades after the final rebalance tick.

Re-ran: the full adaptive suite (368 tests, network-blocked pagination
tests), test_native_faults_min, test_order_throughput,
test_order_contract, test_trading_gates, all six validators
(validate.py, validate_catalogs.py, landscape.py, evidence_manifest.py,
verdict_review_gate.py, trading_gates.py), and both reviewers' mutation
scripts retargeted at this worktree -- all 54 reviewer mutations plus 8
new round-5 mutations killed (two long-documented redundant survivors
from round 4 unchanged: asyncio.run()'s own task cancellation already
covers G6's exact scenario).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…resolve every allocated generation, and harden the warm-monitor preflight

Four low-severity items from the final independent review, plus the test
gaps two of them exposed:

1. An ambiguous-only response (a previously confirmed record whose row
   loses its governing date on a later fetch) wrote an empty list over
   the last good records in complete_attempt, turning a confirmed
   must_flatten back into mere block-and-flag and losing an already-live
   intraday exit signal. Fixed: an ambiguous symbol with zero valid
   records in the new response now degrades (preserving the last good
   records) instead of overwriting them with [].

2. Mutation G4 (drop the scheduler's refresh_timed_out call for a
   two-phase guard) survived because every existing short-timeout test
   drove a legacy single-arg guard, never the real CorporateActionMonitor's
   own generation-aware timeout path. Added a test driving the real
   monitor through a short-timeout scenario, asserting the symbol is
   flagged and the next attempt is allowed after retry_seconds (60s), not
   throttled for the ordinary refresh_seconds (900s).

3. A pre-allocated generation was left permanently unresolved if
   Thread.start() itself raised, or if an unexpected exception escaped the
   worker's own run_attempt call -- refreshes would then be silently
   throttled with nothing ever flagged for up to 900s. Both paths now call
   fail_attempt on the pre-allocated generation before propagating.

4. _preflight_corporate_action_refresh unconditionally awaited
   _schedule_corporate_action_refresh's return value, which is None (not a
   task) when the two-phase begin_attempt itself reports a throttled
   no-op -- a second preflight call on an already-warmed monitor raised
   TypeError from awaiting None. Now only awaits when a task was actually
   created.

The review's own mutation script also caught two gaps in the round-5
fixes themselves once the two-phase API was exercised more directly: G5
(a cancellation mutated to a no-op) survived because the existing test
only checked that a clean end state stayed clean, which held regardless
of whether invalidate_attempt ran; strengthened to assert the generation
counter is actually bumped past the cancelled attempt. A generation-
specific refresh_timed_out check reverted to a round-4-era coarse
heuristic (comparing only _generation to _committed_generation) also
survived; added a test where a stale timeout for an already-superseded
(but not yet committed) generation must be a complete no-op.

Merged origin/main (added #206 and #218 since the round-5 push);
source-hashes.json and manifests/evidence.json were recomputed from
current file content for every changed path, not resolved by picking a
side. No overlap between main's changes and this branch's files beyond
manifests/evidence.json, which merged cleanly.

Re-ran: the full adaptive suite (373 tests) with network-blocked
pagination tests, test_native_faults_min, test_order_throughput,
test_order_contract, test_trading_gates, all six validators (validate.py,
validate_catalogs.py, landscape.py, evidence_manifest.py,
verdict_review_gate.py, trading_gates.py -- all passed), and the
reviewer's own mutate_rev5.py retargeted at this worktree (65 mutations,
all killed except one PATTERN_COUNT=0 entry already superseded by an
equivalent round-5 mutation of my own). Ran gitleaks git history scan
(509 commits, 1.12 GB) with the pinned binary and repo config: all 76,054
findings are pre-existing sourcegraph-access-token/generic-api-key
pattern matches inside the generated docs/ecosystem/index.html artifact;
zero findings in any file this branch touches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ction guard branch

- runner.py: kept both import lines (corporate_actions, credential_guard); the
  two changes touch different regions.
- source-hashes.json: took the union of both sides' keys and recomputed every
  digest from the merged tree. The #219 manifest test passes.
- evidence.json: rebuilt from main's version. Re-registered this branch's
  native-check record, registered corporate_actions.py and its tests, and
  refreshed the six stale digests surgically.

Checks, with the network blocked: 986 adaptive tests OK; native-faults,
order-contract and credential-tools 56 OK; order-throughput, trading-gates,
evidence-manifest and validate 163 OK; all six validators pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

…azily

CI's validate jobs run the suite without alpaca-py, and 12 runner tests failed
with ModuleNotFoundError: main() built AlpacaCorporateActionsSource, and so
imported alpaca-py's data client, on every run, before its gate checks. That
also contradicted the design (the guard acts only with overnight holds).

- runner.main builds the monitor only when session_policy["overnight_holds"]
  is enabled; otherwise the guard is None.
- AlpacaCorporateActionsSource imports and builds its alpaca-py client on
  first use (the _client property). A refused run, or an environment without
  alpaca-py, never needs the SDK. The session timeout and retry setup are
  unchanged and are applied when the client is built.

Checks: CI-like system python gives 363 OK (runner, corporate actions,
sessions). The pinned runtime with the network blocked gives 248 OK
(corporate actions, runner) and 986 in the full suite before the manifest
refresh. The manifest test and the six validators pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins merged commit 4289143 into main Sep 25, 2026
22 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/paper-corporate-action-guard-20260924 branch September 25, 2026 01:33
seathatflowsinourveins pushed a commit that referenced this pull request Sep 25, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Sep 25, 2026
…-09-24/25 merges (#225)

Updates four checkpoint entries: the paper lane, the simulation lane, alpaca-paper-operational and corporate-action-data. They now reflect #198, #199, #212, #218, #219 and #222. tests.test_grand_dashboard passes, and all state strings satisfy progress.py's public-token rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins pushed a commit that referenced this pull request Sep 25, 2026
…porate-action and credential guards

Main moved 19 commits since 5ed8538. The conflicts were resolved by
keeping both sides:

- native_strategy.py: AdaptiveStrategy takes both halted= (E4) and
  corporate_action_guard= (#222). The constructor keeps the halt and
  callback-fault state and the corporate-action guard state.
  cancel_expired keeps the halted-exit skip (E4) and gains #222's
  symbols= immediate-cancel set.
- runner.py: the strategy gets both halted=controller.is_halted and the
  corporate-action guard. The shutdown runs stop_halt_seed() inside
  #222's try/finally, so the corporate-action refresh cleanup still runs
  if the halt-seed stop raises.
- source-hashes.json: the union of both sides' keys, every digest
  recomputed from the merged tree.
- manifests/evidence.json: main's registry, plus the branch's new
  engine-nautilus probe, with all stale digests refreshed. The branch's
  entries for the renamed CI lock (#233) and the renamed grype fixture
  (#224) are dropped.

Checks on the merged tree:
- pinned rc5 runtime, 23 paper modules: 1,320 tests OK (2 skipped);
- full python3 -m unittest: 4,944 tests OK (600 skipped);
- validate.py, evidence_manifest.py --check, and the source-hashes
  manifest test: pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant