Changelog
Every change you can see, newest first. Additive changes never break a client that ignores unknown fields; breaking ones are announced before they ship.
- Additive
Partner tokens carry an audience; MCP channel attribution
- Stored partner access tokens (Redis) gain an optional
audience(api, the default for every token issued before, ormcp). Readers first: only clients from dynamic registration (above) getmcptokens, and registration is turned on only once every pod runs this build; anmcptoken is accepted only through the Cirrus MCP (itsX-Cirrus-Mcp-Keyservice credential, withOMS_MCP_SERVICE_SECRETset and the tenant'sagent_accesson), else 401. - A request carrying an
X-Cirrus-Mcp-Keythat is not the MCP's is refused with 401unauthorized. Requests without the header are unchanged. - With a valid service credential, the MCP's forwarded agent IP (
X-Cirrus-Mcp-Client-Ip) is used for API-key IP allowlists and throttles, and activity recordsvia: mcpwithagentandtool(fields announced in "Activity names the channel and connection"). These headers are ignored on every other request. Deployments withoutOMS_MCP_SERVICE_SECRETsee no change.
- Stored partner access tokens (Redis) gain an optional
- Additive
Stored samples for partner OAuth records
Stored-record fixtures for partner access tokens, connections, apps and refresh tokens, with reader tests. No REST response, webhook or stream message changes.
- Additive
Stored records gain optional created_by
Deployments and v2 trigger records store who created them (
app,key:{id}orpartner:{client_id}). Stored-record change only: no REST response, webhook or stream message shows it yet. Old records read as absent; new fixtures added. - Additive
Strategy, deployment, backtest, risk-limit and basket actions are recorded in activity
Strategy, deployment, backtest, risk-limit and basket actions are now recorded in activity (kinds added 2026-10-30). No request or response shape changes; the kinds are announced in the entry below and ship in the same release as the code that reads them (owner decision). During the rolling deploy (one replica, a window of seconds) a pod still on the previous build reads a record of a new kind as unreadable: it is logged and counted (
infra::unreadable, the watchdog alerts), never dropped, and reads normally once the new build serves. - Breaking
Activity kinds for strategies, deployments, backtests, baskets and connection limits
GET /v1/activityand its filters (kind,category) now accept and may return 16 newkindvalues:strategy_created,strategy_updated,strategy_archived,strategy_version_saved,deployment_started,deployment_paused,deployment_resumed,deployment_stopped,deployment_went_live,deployment_exit_now,strategies_stop_all,backtest_submitted,risk_limits_changed(categorystrategies),basket_saved,basket_deleted(categoryorders) andconnection_limits_changed(categoryaccess).- New
categoryvaluestrategies. - The values are readable and recorded from the same release (the entry above; owner decision, no separate readers-first release). During the rolling deploy (one replica, a window of seconds) a pod on the previous build reports a new-kind record as unreadable (logged and counted, the watchdog alerts); nothing is lost. Notified: 2026-10-31, owner: no external consumer affected — partners with read access (Kuberhunt) only send webhooks and do not read /v1/activity or call /v1/triggers; Pocketful/5Paisa users have no public API; the app moves to /v2 in app-cirrus#322.
- Additive
Activity names the channel and connection
- Activity records (
GET /v1/activity,/v1/activity/{id}, the/v1/streamactivity messages) carry four optional fields onsource, omitted when unknown:client_id(the partner connection,partnersources),via(app,apiormcp),agent(name,version,verified) andtool(forvia: mcp). Records made before this change have none of them.
- Activity records (
- Breaking
margin estimates for brokers without margin calculation
POST /v1/margins/orders(calculate_order_margins): results the broker cannot quote (error_code: not_supported_by_broker: Pocketful, 5paisa, Motilal Oswal, Tradejini, XTS, ODIN) or whose broker call failed now carrytotal_required= an estimate from the exchanges' published risk files where the instrument is in them (wasnull).messageanderror_codeare unchanged on those rows. Rows the estimate cannot price staynull.- New on every result:
source("broker"|"estimate", null without a figure; a closed set),stale(bool) andflags(string list, open: clients ignore values they do not know, e.g.positions_unknown). - New
accounts: one total per account (account,total_required,source,stale,flags,message) for every account that has a result: the sum of the account's results (source: "broker"when all are broker figures). When its orders hedge each other (futures / options of one underlying, or an exchange inter-commodity credit pair), the sum less the estimated hedge benefit (standalone estimates minus the hedged estimate), never below the hedged estimate (source: "estimate"); not when every result is the broker's and the estimate is stale.nullwith amessage("No margin figure for order #N.", 1-based) when any of the account's results has no figure (never a partial sum). - Top-level
total_requiredkeeps its meaning (sum of results with a figure) and now includes estimated results. Notified: not required for Kuberhunt (webhooks only, no webhook/stream/signal change); app-cirrus updated in the same release; API-key users told via the public API changelog when this deploys.
- Additive
cirrus-oms reads the margin tables
- New stored-record kind read by cirrus-oms:
CIRRUS:MARGIN:INSTRUMENTS,CIRRUS:MARGIN:COMMODITIESandCIRRUS:MARGIN:META, written by Nothing users see changes yet. Real samples in cannot read (including a commodity stored under another commodity's field) is reported asmargin_instrument/margin_commodity/margin_metaand left out. A refused META segment reads as stale;keptwith today'strade_dateis current.
- New stored-record kind read by cirrus-oms:
- Change
Pocketful refuses an order-book reply with no orders list; 10-30 evening ledger
Behaviour (no field added, removed or retyped; no new enum value)
- Pocketful order book: a 200 reply whose
dataholds other fields but noorderslist (once, its funds view, on a pending read: Cirrus prod 2026-10-30, recordingorder_book-120cc967) now fails the read (unexpected response), read again on the next poll, instead of reading as an empty pending list that made every working order look gone. No existing expectation changed. - Paper error classes: cancel 400 "Order is CANCELLED and cannot be cancelled" is
order_not_modifiable(book read back, as the COMPLETE refusal; the catalogue'sw.cancel_twice); place 503 "Request timed out" isoutcome_unknown(paper-go's 2 s handler deadline; the order may exist, the book decides); order-book 503 "Request timed out" istransient(a read, as the gateway timeouts on reads). - Conformance packs: new production recordings (Zerodha BFO / NSE CNC /
gttp-meta order book rowsorder_book-d5ac9118, BFO positionspositions-b3b1abc6and tradestrades-357184d0, theinstruments_metaframews_ignore-179a896b; paper transport timeoutsgtt_list-6d559932,place_order-70672464,modify_order-9a5b7481; paper refusalsplace_order-96540235,order_book-53e95c79,cancel_order-dfca95df). Additive.
- Pocketful order book: a 200 reply whose
- Change
NSE holidays in every tenant profile
Behaviour (no field added, removed or retyped; no new enum value)
- The tenant profiles listed no NSE holidays, so strategies, the backtest calendar and volatility lookbacks treated NSE holidays as trading days. They now list the 2026 NSE holidays. Backtests whose lookback crosses a holiday give slightly different numbers: the recorded
/v2/backtestsexamples were re-recorded (values only).
- The tenant profiles listed no NSE holidays, so strategies, the backtest calendar and volatility lookbacks treated NSE holidays as trading days. They now list the 2026 NSE holidays. Backtests whose lookback crosses a holiday give slightly different numbers: the recorded
- Additive
atm_iv, iv_rank and iv_pct conditions are evaluated
GET /v2/strategies/capabilities(get_auto_strategy_capabilities): with indicator conditions on, the functionsatm_iv,iv_rankandiv_pctareavailable: true(werefalse); off, unchanged. Strategy documents using them now validate without the "not available yet" blocker;atm_iv's expiry is'weekly'/'monthly'with an optionaln(0 = current, 1 = next, up to 3), other values are refused with the message. Units:atm_ivvol points,iv_rank/iv_pct0–100. The recorded examples (indicators off) are unchanged.- Backtest results: option legs on real 1-minute option candles where GreptimeDB has them; new
labels.text"Partly estimated: ..." and notes for runs with some modelled days, and a source row (greptime_options). No field changes.
- Additive
delta_hedge@1 released (preview); high-IV condor hedges its delta
GET /v2/strategies/components(list_auto_strategy_components):delta_hedge@1isstatus: preview,phase: a,deployable: true(wasplanned,c,false); itsdelta_bandtrigger variant isx-phase: a(wasc).GET /v2/strategies/capabilities: thedelta_bandtrigger isphase: a,deployable: true. The document schema (strategy.schema.v1.json) carries the samex-phase.- Behaviour:
POST /v2/strategies/validateno longer gives thenot_available_yetblocker fordelta_hedge@1(anet.deltacondition still gets its own blocker; usetrigger: {on: delta_band}). A version withdelta_hedge@1on adelta_bandtrigger deploys. GET /v2/templates(list_auto_strategy_templates): thehigh_iv_iron_condortemplate gains a third adjustment,delta_hedge@1ondelta_band0.5,target0.1, with the future, 300 s apart, at most 3 a trade; its document description says so.
- Additive
Backtest sweeps with overfitting warnings
- New operations
POST /v2/backtests/sweeps(create_auto_backtest_sweep),GET /v2/backtests/sweeps(list_auto_backtest_sweeps),GET /v2/backtests/sweeps/{sweep_id}(get_auto_backtest_sweep) andDELETE /v2/backtests/sweeps/{sweep_id}(cancel_auto_backtest_sweep): one backtest per version of up to 3 settings (JSON pointers into the strategy document, at most 200 versions), then the recommended version (best plateau),warnings,notes, deflated Sharpe, PBO and walk-forward. Errors reuseinvalid_request(fieldaxes),version_not_found,backtest_limit_reached,backtest_not_foundandbacktest_finished. A sweep counts as 1 + versions/20 of the 50 backtests a day and as one of the 3 running at once; one sweep at a time per user (409backtest_limit_reached, "One sweep at a time"). - Backtest workers share fairly: at most 3 of a user's backtests run at once, a sweep's versions included, and the user with the fewest running goes next, so a sweep never holds other users' backtests back.
GET /v2/backtestsno longer lists a sweep's versions (each is still readable by id, with its trades and cycles); nothing listed before is left out.
- New operations
- Change
Pocketful delivery positions report no realised P&L
Behaviour (no field added, removed or retyped; no new enum value)
- Pocketful positions: a CNC or MTF row now reports
realised_pnlabsent, as a carried row does. Pocketful'srealized_mtmbooks nothing on delivery rows (a sale out of holdings reads 0: recordingspositions-5f192576,positions-089eea71), so a closed delivery row (a same-day CNC round trip, an MTF buy sold the same day) disagreed with its overall P&L:position.realised_on_overall_basiskept firing on the Pocketful tenant after the carried-row fix started (prod 2026-10-30).pnlis unchanged. Expectations re-recorded, onlyrealised_pnl: 0.0removed on the delivery row:positions-089eea71,1168e67f,346c366a,5f192576,89d6b288,957afbcd,d21babfb,f937538b. - Conformance packs: new production recordings (AngelOne order book row and modify request, Kotak Neo BSE holdings, Pocketful MTF order / trade rows and seven order-feed frames, a paper modify refusal). Error templates: paper modify "Order is CANCELLED and cannot be modified" is
order_not_modifiable(as the COMPLETE refusal); AngelOne order book HTTP 404 "not found" isoutcome_unknown(owner decision 2026-10-29, now with its template).
- Pocketful positions: a CNC or MTF row now reports
- Additive
Margin-check journey entries for automated entries
- A live automated entry that goes out with no margin pre-check writes one journey entry of the new kind
margin_unchecked("Margin was not checked before this entry: ..."): when its broker gives no margin quote, or when the broker's margin quote fails ("<Broker>'s margin check didn't answer"). The orders still go out as before and the broker checks funds; nothing is refused. - New ops anomaly kinds (no user notice):
auto_fill_swept(a fill the entry's own report missed was reported by the late-fill sweep) andgtt_cancel_not_found(a v1 GTT cancel answered not found and the GTT is unlisted; the record stays pending and re-checks back off to 15 min). - A broker-held protection GTT (a
/v1/gttrecord or an entry's protection) is read back from the broker every 2 min. Showntriggered, its record endsfired_at_broker; shown cancelled / deleted / expired / disabled / rejected without firing, its record ends with the existingstatus: cancelled(copied from the broker, nothing re-placed; its journal says "<Broker> shows this GTT <status>" and, once the notice is queued, "Notified you: stop-loss removed at <Broker>" with "Cancelled at <Broker> (GTT status: <status>), not by Cirrus. You were notified; this position is no longer protected."), the user gets the existingprotection_failedactivity / notice ("Your stop-loss for <symbol> was removed at <Broker> (<status>); this position is no longer protected."), and, for a GTT protecting an entry's position only, ops the new anomaly kindbroker_protection_removed(once per record, paged; a user's own/v1/gttdeleted in the broker app is not paged). Absent from the broker's list or unreadable: kept. TriggerView(/v1/triggers, list and detail) has a new optional fieldended_message: why an ended trigger ended, in a plain sentence, when the server has one; omitted otherwise (no existing field or value changed). Set on the broker-removed GTT path above ("Cancelled at <Broker> (GTT status: <status>), not by Cirrus. You were notified; this position is no longer protected."). Webhooks unchanged.- Tenant profiles may set
strategies.live_brokers; live deploys on other brokers answer the existing 403live_not_enabled. Unset on every tenant.
- A live automated entry that goes out with no margin pre-check writes one journey entry of the new kind
- Additive
Conformance acks live in the repository
- Conformance packs gain
acks.toml([[ack]]key, note, by, date): ledger keys acknowledged on every tenant. At release start the sweep turns anewbroker_unhandledentry whose key is listedacknowledged, and the writer records a first sighting of such a keyacknowledged, so it never alerts. Ahandledentry stays handled; an entry a tenant acknowledged keeps its own note. - Stored record
broker_unhandled:ackgains an optionalsource("repo"when it came fromacks.toml). Readers take the document as it is (admin list, watchdog, CLI), so old and new pods read both. conformance ack <key> --notealso appends the key to its pack'sacks.tomland says to commit it; an entry already acknowledged on that tenant is added to the file only.- Back-filled 44 keys (one shape or message each) from the acknowledged entries on Cirrus prod and the Pocketful tenant, notes kept.
- Conformance packs gain
- Change
Pocketful carried positions report no realised P&L
Behaviour (no field added, removed or retyped; no new enum value)
- Pocketful positions: a row with a carried quantity (
cf_buy_quantityorcf_sell_quantityabove 0) now reportsrealised_pnlabsent, as Zerodha does. Pocketful'srealized_mtmbooks only today's own trades, so a closed carried row read 0 (or a part) while its overall P&L was booked (Pocketful tenant prod 2026-10-28, 871position.realised_on_overall_basis). Rows with no carried side keeprealized_mtm.pnlis unchanged. Expectations re-recorded:positions-8f2cb23b,cf26a31c,dbfc1648,ea1fd1ca(onlyrealised_pnl: 0.0removed). - Pocketful place refusals "you're not allowed to place order in exchange 'BFO' / 'NFO'" (an account's segment not enabled) are now
rms_block(wereoutcome_unknown), as Zerodha's and 5paisa's segment refusals; expectationsplace_order-65d79de3,place_order-96ac3a6f.
- Pocketful positions: a row with a carried quantity (
- Change
Pocketful: triggered stop reads OPEN
Behaviour (no field added, removed or retyped; no new enum value)
- Pocketful order book: a stop whose trigger was hit (
order_statusSL_TRIGGER_CONFIRMED, relisted as LIMIT and working at its limit) now readsOPEN(PARTIALLY_FILLEDonce something filled) instead ofTRIGGER_PENDING, as paper's triggered SL resting at its limit does. A broker stop that gapped past its limit then shows as went-off, so Cirrus cancels it and exits at its level. Users see the order's state asOPENin the order book andorder_updatewebhooks. Five expectations re-recorded (order_book-8fd7038c,-ea09119a,-f2740bcb,-8f11205e,-c103a838): only the state changed.
- Pocketful order book: a stop whose trigger was hit (
- Additive
Pocketful order book and book lookup rows since 16 Oct
- Conformance: 94 Pocketful row types seen in production on both tenants since 2026-10-16 (52 order_book and 2 more from the Cirrus tenant, 40 book_lookup) are handled types, each with a promoted recording. They are new combinations of the categorical fields (status, product, order type, side, exchange, execution type, leg indicator) on row shapes already known; no new field. Every mapped row reads as before: state from its list and status, filled quantity and average price as sent.