@battlegrid/mcp-server 31.2.25 → 31.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,16 +24,72 @@ Seeing package `31.x` alongside handshake `battlegrid@33.x` — the package **be
24
24
 
25
25
  **What this changes for you:** nothing about how you call anything. Upgrading the package no longer waits on a server deploy, and a server deploy no longer strands you on a package that names the wrong contract — reconnect and the announcement follows. **Contract breaking-change notes are no longer keyed to package versions**, since a contract move is no longer a release here; the v11-and-earlier notes below are kept as history, and the live vocabulary is always discovery.
26
26
 
27
- ## Contract history — v59
27
+ ## Contract history — v61
28
+
29
+ One part to read first: **the per-condition evidence clock is gone**, and what replaced it is not a
30
+ rename. Which bar a condition reads is now decided by the surface asking — a decision reads completed
31
+ strategy bars, a display read shows the forming one — and by each candle column's own Confirmed /
32
+ Developing selector. `closes` survives and changes meaning. The canonical record for every contract
33
+ move is `docs/architecture/MCP_CONTRACT_HISTORY.md` in `battlegrid-app`; the served version is what
34
+ the handshake announces.
35
+
36
+ ### Rejected input — something you author is no longer accepted
37
+
38
+ - **A condition entry carrying `clock` is REFUSED** (61.0.0, `read-higher-timeframes-per-column`).
39
+ The authoring schemas are `.strict()`, so `compile_strategy_plan`, `apply_strategy_plan`,
40
+ `fork_strategy` and the HTTP save alike fail the body with the unknown-key error. There is no
41
+ replacement key to send: the decision instant belongs to the caller, not to the condition. Two
42
+ input hashes move, `compile_strategy_plan`'s and `preview_strategy_report`'s — the two tools that
43
+ accept a condition entry.
44
+
45
+ - **`closes` stays mandatory and means something new.** It is now *held for N completed strategy
46
+ bars* (1–5). Above `1` it is legal only over a header a completed bar actually moves, at or above
47
+ the strategy timeframe, never over a developing read, and only where the condition carries a clause
48
+ of its own — a referenced condition resolves once and contributes the same answer to every bar, so
49
+ a hold reached only through a reference would count reads that never happened.
50
+
51
+ ### Changed shape — what you receive moves
52
+
53
+ - **`ConditionOutcome.closeClock` becomes `hold`, and it is NON-NULL for every condition.** A
54
+ one-close condition reads `0 or 1 of 1` rather than serving an absence, so a client no longer
55
+ branches on whether the reading exists. The count is taken from completed strategy bars whatever
56
+ basis the surface evaluated on.
57
+
58
+ - **`ReportConditionColumnDTO.closeClockReadable` becomes `closesReadable`, beside a new
59
+ `developingRead`.** The first answers whether a condition addressing that header may hold more than
60
+ one completed bar; the second states whether the header reads the bar still in progress at the
61
+ decision instant. Both are server-supplied — derive neither. Eight output hashes move, covering
62
+ every tool that serves a strategy's conditions or a report's addressable columns.
63
+
64
+ ### Wider input — nothing you send today breaks
65
+
66
+ - **The `bars` selector is declared on eleven candle-series transforms, not four.** `value`,
67
+ `classifyZone`, `classifyState`, `distance`, `spread`, `crossDetect` and `bandTouch` join
68
+ `trajectory`, `aggregate`, `efficiency` and `maxShare`. It carries **no default value**, because
69
+ the default is a rule rather than a constant: Confirmed (`"closed"`) above the strategy timeframe,
70
+ and at or below it the series as the frame carries it. The resolved answer is served per column on
71
+ `effectiveParameters.bars` — `null` where discovery has no anchor to resolve a rung against.
72
+
73
+ - **A higher-timeframe level is measured from the current strategy-bar price.** Distances and the
74
+ candle label classifiers compare against the frame's current price rather than the higher-timeframe
75
+ bar's own close, which could be a full higher-timeframe bar stale. A distance chained into a series
76
+ keeps every slot on its own bar's close.
77
+
78
+ ### Vocabulary
79
+
80
+ `domains.conditionClock` is removed; `domains.columnBars: ["all", "closed"]` takes its place, and
81
+ `axes.condition` loses `clock`. `toolCount` stays 117.
82
+
83
+ ## Contract history — v60
28
84
 
29
85
  One release train, four numbers, and two parts to read first: **`propose_entry_decision` no longer
30
- decides in the call**, and **a closed trade's `tradeStatus` now follows its net P&L**. v55 through v58 are not written up here; the canonical record for
86
+ decides in the call**, and **a closed trade's `tradeStatus` now follows its net P&L**. v55 through v59 are not written up here; the canonical record for
31
87
  every contract move is `docs/architecture/MCP_CONTRACT_HISTORY.md` in `battlegrid-app`, and the
32
88
  served version is what the handshake announces.
33
89
 
34
90
  ### Rejected input — something you author is no longer accepted
35
91
 
36
- - **The entry axis names no bar but the strategy's own, and three of its keys are gone** (59.0.0,
92
+ - **The entry axis names no bar but the strategy's own, and three of its keys are gone** (60.0.0,
37
93
  `decide-entry-on-strategy-close`). The authoring schemas are `.strict()`, so `compile_strategy_plan`,
38
94
  `apply_strategy_plan`, `fork_strategy` and `restore_strategy` REFUSE a body carrying
39
95
  `entry.confirmTf` (the deciding bar is the strategy's own timeframe, so a required input whose only
@@ -43,7 +99,7 @@ served version is what the handshake announces.
43
99
  decided close), and the whole `exit` object (the open-position exit lane judges one closed candle
44
100
  of the position's strategy timeframe). Exactly one input hash moves, `compile_strategy_plan`'s.
45
101
 
46
- - **`entry.trigger: AT_SIGNAL` is retired for authoring** (59.0.0, `retire-at-signal-trigger`, riding
102
+ - **`entry.trigger: AT_SIGNAL` is retired for authoring** (60.0.0, `retire-at-signal-trigger`, riding
47
103
  the same number). Refused on every authoring surface and on a RESTORE, which rebuilds a stored
48
104
  revision through the same value object. The enum member stays READABLE on every strategy read and
49
105
  on a fired decision's provenance, so a pre-retirement revision is still legible — it just cannot be
@@ -51,7 +107,7 @@ served version is what the handshake announces.
51
107
 
52
108
  ### Changed meaning, unchanged shape
53
109
 
54
- - **`tradeStatus` follows NET P&L on every closed trade** (59.3.0, `label-trade-outcome-by-pnl`).
110
+ - **`tradeStatus` follows NET P&L on every closed trade** (60.3.0, `label-trade-outcome-by-pnl`).
55
111
  No schema hash moves for this and the values you receive change anyway. It used to map the close
56
112
  REASON onto a verdict — every `TAKE_PROFIT` was `WON`, every `STOP_LOSS` was `LOST`, and a
57
113
  `MARKET_CLOSE` of either sign was the neutral `CLOSED` — so a stop that filled after a break-even
@@ -60,7 +116,7 @@ served version is what the handshake announces.
60
116
  only that there is no outcome row to judge. A client that counted `WON` rows was counting
61
117
  take-profits.
62
118
 
63
- - **A proposal is QUEUED, not decided** (59.2.0, `queue-manual-entry-for-close`). This is the entry in
119
+ - **A proposal is QUEUED, not decided** (60.2.0, `queue-manual-entry-for-close`). This is the entry in
64
120
  this section to act on. `propose_entry_decision` registers a request against the agent's next
65
121
  strategy-bar close and returns immediately: no model runs, nothing is spent, and the call carries
66
122
  `type: "queued"` with `request` — `requestId`, the bar (`barStart`), when the answer is due
@@ -71,22 +127,22 @@ served version is what the handshake announces.
71
127
 
72
128
  `recommendation` and `no_trade` REMAIN in the union: the idempotency registrar replays results
73
129
  recorded before this release for their TTL, so a client that dropped those members would fail on
74
- its own retry. **A client written against 59.2 handles `queued` and `error`**; one that must also
130
+ its own retry. **A client written against 60.2 handles `queued` and `error`**; one that must also
75
131
  replay handles all four.
76
132
 
77
133
  ### Reshaped output — the same call returns a different shape
78
134
 
79
- - **The four retired entry keys leave every strategy read** (59.0.0) — `get_strategy`,
135
+ - **The four retired entry keys leave every strategy read** (60.0.0) — `get_strategy`,
80
136
  `list_strategies`, `fork_strategy` and both plan envelopes — and `confirmTimeframesByMainCandle`
81
137
  leaves `list_strategy_vocabulary`, because there is no confirm set left to publish.
82
138
 
83
- - **`entryDiscipline.closes` and `.bandAtrMultiple` go `number` → `number | null`** (59.0.0) on
139
+ - **`entryDiscipline.closes` and `.bandAtrMultiple` go `number` → `number | null`** (60.0.0) on
84
140
  `get_trade_outcome_by_decision` and `list_trade_outcomes`. Null on every decision fired after this
85
141
  release, which authors neither; a non-null pair dates the row to the arming era.
86
142
 
87
143
  ### Widened enum — new members your own copy rejects
88
144
 
89
- - **`TradeConvErrorCode` gains `REQUEST_PENDING`** (59.2.0), on the SURFACE arm of
145
+ - **`TradeConvErrorCode` gains `REQUEST_PENDING`** (60.2.0), on the SURFACE arm of
90
146
  `propose_entry_decision`'s error: one pending request per user and coin, so a second is refused. A
91
147
  coin already carrying a pending or live position is refused before anything is queued, as an
92
148
  ENGINE-origin `OPEN_POSITION_CONFLICT` — a member that was already published, reaching this surface
@@ -94,7 +150,7 @@ served version is what the handshake announces.
94
150
 
95
151
  ### Additive in the same span
96
152
 
97
- - **The exit names the leg that filled** (59.3.0, `label-trade-outcome-by-pnl`). `TradeOutcomeDTO`
153
+ - **The exit names the leg that filled** (60.3.0, `label-trade-outcome-by-pnl`). `TradeOutcomeDTO`
98
154
  and the pipeline outcome summary gain `exitRepriceSource`: the reprice that placed the protection
99
155
  leg which actually closed the position — `BREAK_EVEN`, `TRAILING`, `TIME_DECAY`,
100
156
  `MANUAL_OVERRIDE`, `UPDATED` — or null on every close no protection leg filled and on a leg that
@@ -102,12 +158,12 @@ served version is what the handshake announces.
102
158
  before calling a stopped-out trade a failure. The enum is the one `get_position_audit_history`
103
159
  already publishes. Four output hashes move; no input schema does.
104
160
 
105
- - **Two tools join the catalog for the request lifecycle** (59.2.0, `queue-manual-entry-for-close`).
161
+ - **Two tools join the catalog for the request lifecycle** (60.2.0, `queue-manual-entry-for-close`).
106
162
  `get_entry_request` (read scope) reads a request that is still pending and is `NOT_FOUND` once it
107
163
  has been answered, cancelled or expired — the answer is in the conversation, not there.
108
164
  `cancel_entry_request` (`mcp:wager`) withdraws one before its bar is decided. `toolCount` 115 → 117.
109
165
 
110
- - **Three radar reads publish the close decision** (59.1.0, `add-radar-close-decision-state`).
166
+ - **Three radar reads publish the close decision** (60.1.0, `add-radar-close-decision-state`).
111
167
  `get_radar_deployment`, `list_radar_deployments` and `preview_radar_resolution` carry one further
112
168
  key on `resolvesNow`: `closeDecision`, non-null whenever an agent is on duty for the pair. It
113
169
  carries the deciding `timeframe`, the `nextCloseAt` instant, a `state` of `WAITING` or `DEFERRED`
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ import { type Implementation, type Prompt, type Resource } from '@modelcontextpr
49
49
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
50
50
  * correction. Never move it to track the server.
51
51
  */
52
- export declare const PACKAGE_VERSION = "31.2.25";
52
+ export declare const PACKAGE_VERSION = "31.2.27";
53
53
  export declare const DEFAULT_URL = "https://mcp.battlegrid.trade/mcp";
54
54
  export interface EnvConfig {
55
55
  apiKeys: string[];
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema
52
52
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
53
53
  * correction. Never move it to track the server.
54
54
  */
55
- export const PACKAGE_VERSION = '31.2.25';
55
+ export const PACKAGE_VERSION = '31.2.27';
56
56
  export const DEFAULT_URL = 'https://mcp.battlegrid.trade/mcp';
57
57
  const MAX_RETRIES = 3;
58
58
  const RETRY_DELAYS_MS = [2000, 4000, 8000];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@battlegrid/mcp-server",
3
- "version": "31.2.25",
3
+ "version": "31.2.27",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "generator": "battlegrid-app/server/scripts/export-mcp-skills.mjs",
3
- "contractVersion": "59.3.0",
3
+ "contractVersion": "61.0.0",
4
4
  "files": {
5
5
  "battlegrid-agent-management/SKILL.md": "b6ea75b3d838c1dbaaf3984d7e99a11c81686de2c1a1910c35699ee7e70dafa5",
6
6
  "battlegrid-arena-play/SKILL.md": "03fa153bf82be18bf5ed01e3ba12e2cd9f99a81b08d903a77b52169e7519a182",
7
7
  "battlegrid-market-analysis/SKILL.md": "22fde9c4eac0261c89a8056ab8b20b66fcbe1fd58447fa0ac84fd410b4d134d7",
8
8
  "battlegrid-radar-deployment/SKILL.md": "d2a012b154e1e529db19529bd7c3d75d069f4e410d5804add51165f0f0902b2e",
9
- "battlegrid-strategy-authoring/SKILL.md": "ea2593ea9d6d2feb9910e285e4b132eb36001164b9d6551cc206531afad3f552",
9
+ "battlegrid-strategy-authoring/SKILL.md": "93e1c9709079a4e9c968597ac572b4b62f5b0e68c01f9e9947db7e2ba304dcdf",
10
10
  "battlegrid-strategy-doctor/SKILL.md": "9ab3f79128ec81d3e90167c3f1ea4bd6f6b570290e5ebe2bf855195263c58239",
11
- "battlegrid-strategy-examples/SKILL.md": "263c3b865ea85e56ac420611a7070360d65be6f2faee071329880e49a6101eda",
11
+ "battlegrid-strategy-examples/SKILL.md": "17e8cf8040813622bd5b82165c7af82e03287fee1f6aac974e0b6c584580dd27",
12
12
  "battlegrid-strategy-examples/references/playbooks.md": "35c543c090d00ba19e4bcf0a80502afac7a4954643fc535de0b24381c91ecbf4",
13
13
  "battlegrid-strategy-examples/references/recipes.md": "53887daece17995543e7d3fe1bf2577fc41a6254c291acacde48d7de63853641",
14
14
  "battlegrid-strategy-examples/references/tradingview-ports.md": "2b13adbc137178955edcca31b6ab40c01c745a932a0750b91db8820c736700bb",
@@ -104,9 +104,9 @@ question you would otherwise guess — and a refusal you would otherwise earn:
104
104
  `levelOffsetAtrMultiple`, `validForBars`. Every trigger is decided at the close of the strategy's
105
105
  OWN bar, so there is no confirm-timeframe key; for the level triggers the level itself is derived
106
106
  from the trigger and the trade's direction, never named. A multi-bar hold belongs to the condition
107
- that needs it (`clock: CLOSE` with its own `closes`), not to this axis. The `strategy-examples`
108
- skill carries the vocabulary and the one-directional legality matrix; a CREATE without it is
109
- refused outright.
107
+ that needs it (its own `closes`, counted in completed strategy bars), not to this axis. The
108
+ `strategy-examples` skill carries the vocabulary, the hold's legality and the per-column
109
+ Confirmed / Developing read; a CREATE without it is refused outright.
110
110
  - `get_strategy_column_contract` → `outputs[].conditionOperators`. An empty array means that
111
111
  rendered header has no comparison semantics and cannot appear in a condition clause at all.
112
112
  Legality is per rendered header, not per column: a trajectory's slot header and its `_trend`
@@ -86,8 +86,9 @@ against a literal; neither of those shapes is what a previous-session level need
86
86
 
87
87
  ## Conditions
88
88
 
89
- `{ conditionKey, name, definition, verdict, required, exit, clock, closes }` — all eight
90
- required, no defaults. Clauses: numeric/rank headers take `lt|lte|gte|gt|between`;
89
+ `{ conditionKey, name, definition, verdict, required, exit, closes }` — all seven
90
+ required, no defaults. A `clock` key is REFUSED: the per-condition evidence clock was retired in
91
+ contract `61.0.0`. Clauses: numeric/rank headers take `lt|lte|gte|gt|between`;
91
92
  classification/direction headers take `is|in` with the served vocabulary. Groups:
92
93
  `ALL | ANY | NOT | N_OF` (with `n`), depth ≤ 2. `conditionRef` composes named conditions (no
93
94
  cycles; forward refs legal). `sectionKey: null` is sugar for a report-unique header only.
@@ -103,30 +104,53 @@ before billing. And the RESOLVED verdict binds entry DIRECTION: `UP` admits long
103
104
  setups block and from the `decide_trade` contract, not merely discouraged in them. A strategy that
104
105
  declares no verdict-carrying condition resolves `null` and constrains nothing.
105
106
 
106
- A verdict carrier must read a SETTLED bar wherever one is available to it — `clock: "CLOSE"`
107
- whenever every column in its CLOSURE accepts a closed frame. The closure is what it reads directly
108
- plus everything reached through `conditionRef`, transitively: a referenced condition contributes what
109
- IT reads, never the clock it happens to declare, so moving a clause into a building block and leaving
110
- that block `LIVE` does not make a settled bar unavailable to the carrier. Where no closed frame moves
111
- some operand in the closure (a published regime label, an open-interest regime, a published rolling
112
- change), `LIVE` stays legal at any depth: there is no settled bar to take.
113
-
114
- Evaluation is three-valued: UNRESOLVED never collapses to FALSE; forming-bar reads are provisional.
115
-
116
- **The evidence clock.** `clock: "LIVE"` reads the forming bar; `clock: "CLOSE"` reads settled
117
- bars, and `closes` is how many consecutive closed bars must read TRUE (1–5) — always `1` under
118
- LIVE, which has exactly one frame. A CLOSE clock is legal **only** over a header resolved from
119
- this coin's own candle series at offset 0. Frame-inert operands are refused
120
- (`CONDITION_CLOCK_OPERAND_ILLEGAL`): perp-payload scalars, published rolling changes, ranks, zone
121
- entities, MDS regime labels, enrichment metrics, session scalars, and any clause authored at a
122
- non-zero offset. A closed frame cannot move them, so "held for N closes" would describe reads
123
- that never happened. A frame-inert operand anywhere in a condition's closure simply keeps that
124
- condition on `LIVE`, and that is legal — splitting the clause into its own condition and
125
- `conditionRef`-ing it does NOT buy the referencing condition a CLOSE clock, because a `CLOSE`
126
- condition may not reference a `LIVE` one (`CONDITION_CLOCK_REFERENCE_ILLEGAL`) and availability walks
127
- into the referenced closure anyway. **Worked liquidity floor:** `LIQUID_FLOOR` is `LIVE` because
128
- `vol24hUsd` is a bundle scalar, and a condition that references it is `LIVE` too. Reach for a split to
129
- keep a condition's MEANING separable, not to change its clock.
107
+ A verdict carrier must read no DEVELOPING bar. A verdict is refused over any closure that reads a
108
+ column selecting Developing above the strategy timeframe (`CONDITION_VERDICT_READ_ILLEGAL`). The
109
+ closure is what the condition reads directly plus everything reached through `conditionRef`,
110
+ transitively: a referenced condition contributes what IT reads, so moving the clause into a building
111
+ block does not launder it. A closure whose candle columns all read Confirmed — or whose operands no
112
+ bar moves at all, such as a published regime label, an open-interest regime or a published rolling
113
+ change — admits a verdict at every decision, because a decision reads the completed strategy bar and
114
+ a level is compared against that bar's close.
115
+
116
+ Evaluation is three-valued: UNRESOLVED never collapses to FALSE; a developing read is provisional,
117
+ and so is any reading taken on the display lane's forming bar.
118
+
119
+ **Every condition is decided at the strategy bar's close.** There is no per-condition clock. Which
120
+ bar a condition reads is decided by the surface asking — a decision (the radar's close decision, the
121
+ compose that takes the trade, the exit sweep) reads completed strategy bars; a display read shows the
122
+ forming one and decides nothing — and by each candle column's own Confirmed / Developing selector.
123
+
124
+ **The hold count.** `closes` is how many consecutive COMPLETED strategy bars must read TRUE (1–5). A
125
+ hold counts the same completed bars whichever surface asks, so a held condition reads them on the
126
+ display lane too. Above `1` it is legal **only** where a completed bar changes the reading: over a
127
+ header resolved from this coin's own candle series, at offset 0, at or above the strategy timeframe,
128
+ read Confirmed. Everything else is refused (`CONDITION_HOLD_OPERAND_ILLEGAL`) — perp-payload scalars,
129
+ published rolling changes, ranks, zone entities, MDS regime labels, enrichment metrics, session
130
+ scalars, a non-zero offset, a column BELOW the strategy timeframe (the retained lower series cannot
131
+ reach an earlier strategy close), and a developing read (the bar in progress exists on the newest
132
+ frame only). A hold also needs a clause of the condition's OWN
133
+ (`CONDITION_HOLD_ILLEGAL`): a referenced condition is resolved once and contributes the same answer
134
+ to every frame, so a hold that moves with the bar only through a reference would count reads that
135
+ never happened. **Worked liquidity floor:** `LIQUID_FLOOR` holds one close because `vol24hUsd` is a
136
+ bundle scalar. Reach for a split to keep a condition's MEANING separable, not to buy it a hold.
137
+
138
+ **Confirmed / Developing, per candle column.** Every transform whose home is the coin's candle series
139
+ carries `bars`: `"closed"` is Confirmed — the newest bar completed at the strategy close — and
140
+ `"all"` is Developing, the higher-timeframe bar still in progress at that close, which REPAINTS until
141
+ it completes. Left unset the default is a rule, not a value: Confirmed for a column ABOVE the
142
+ strategy timeframe, and at or below it the series as the frame carries it. At or below the strategy
143
+ timeframe the choice therefore sets only what the live lane shows — no bar is in progress at the
144
+ anchor's own close — so Developing there is legal and changes no decision. A condition reading a
145
+ developing bar is single-frame: it holds one close, carries no verdict and cannot take the `exit`
146
+ role. The resolved answer for a column is served on `effectiveParameters.bars`.
147
+
148
+ **A higher-timeframe level is measured from the current strategy-bar price.** The distance to a `4h`
149
+ Donchian band, and every candle label classifier (`MA_ALIGN`, `PRICE_ZONE`, `BB_TOUCH`), compares
150
+ against the frame's current price — the strategy bar's close under a decision, the live mark on
151
+ display — not against the higher-timeframe bar's own close, which is up to one higher-timeframe bar
152
+ stale. A distance CHAINED into a series transform keeps every slot on its own bar's close, so one
153
+ series carries one reference basis.
130
154
 
131
155
  **The lane a strategy is deployed to.** Report-level scalars split by LANE, and the split is not a
132
156
  quality of the header — it is which reader runs. Market breadth and the reference pairs are ordinary
@@ -138,10 +162,12 @@ can read instead. The refusal lands at DEPLOY rather than at save, because a str
138
162
  of its own: the same strategy is legal, and reads those scalars correctly, on an arena agent.
139
163
 
140
164
  **The exit role.** `exit: true` makes a settled TRUE reading close open positions its verdict
141
- opposes — UP exits SHORTs, DOWN exits LONGs, a NEITHER or `null` verdict exits both. Legal only
142
- under `clock: "CLOSE"`: a LIVE reading is the forming bar, and an exit fired on one is an
143
- intrabar exit. Orthogonal to `required` — the two act on disjoint lifecycles, pre-entry versus
144
- open — so a condition may carry both, either, or neither.
165
+ opposes — UP exits SHORTs, DOWN exits LONGs, a NEITHER or `null` verdict exits both. Legal only over
166
+ a closure every operand of which a completed bar MOVES (`CONDITION_EXIT_READ_ILLEGAL`), which rules
167
+ out a developing read — a bar in progress is the forming bar, and an exit fired on one is an intrabar
168
+ exit — and rules out a frame-inert operand, which could never fire. Orthogonal to `required` — the
169
+ two act on disjoint lifecycles, pre-entry versus open — so a condition may carry both, either, or
170
+ neither.
145
171
 
146
172
  **A state column is not a flip event.** `ST_DIR` reads the same on every bar of a trend, so
147
173
  `ST_DIR is "bullish"` is a regime filter and never an entry signal. The flip needs an event column
@@ -163,7 +189,7 @@ is DERIVED from the trigger and the trade's direction, never named. There is no
163
189
  key either: the bar whose close decides an entry is the strategy's OWN timeframe.
164
190
 
165
191
  **Every trigger is decided at the close of the strategy's own bar — there are three, and the close
166
- is the only entry clock.** The newest settled bar is read on the closed basis — every `LIVE`-clocked
192
+ is the only entry clock.** The newest completed bar is read on the closed basis — every one-close
167
193
  condition resolves on that bar and the scorecard reads its close — and a reading that still qualifies
168
194
  fires at that close. A bar that does not qualify decides nothing and is not revisited. The fill lands
169
195
  at the next tick, and the platform refuses it if the market has already run past its own drift budget
@@ -180,8 +206,8 @@ and is REFUSED on every save; there is no live-reading entry to author.
180
206
  short) by the offset, waiting for a return to it. Not filling is a correct outcome, not a
181
207
  failure; no unrecovered break in memory means no setup, never a fallback level.
182
208
 
183
- **A multi-bar hold belongs to the CONDITION that needs it.** Declare `clock: CLOSE` with that
184
- condition's own `closes` — the entry axis counts no bars, and there is no displacement band: the
209
+ **A multi-bar hold belongs to the CONDITION that needs it.** Declare that condition's own `closes` —
210
+ the entry axis counts no bars, and there is no displacement band: the
185
211
  platform's entry-deviation gate measures the live mark against the decided close and refuses a fill
186
212
  that drifted past the budget in either direction.
187
213