docs: SessionFrankfurt output semantics + declared-tap authoring (#285)
Surfaced by the measurement-milestone fieldtest: a downstream author reaching for session anchoring or declared taps had no public statement of either — the semantics lived only in node source / the design ledger. SessionFrankfurt / Session output semantics: - glossary `session node`: corrected — it claimed three streams (`bars_since_open`, `in_session`, `session_open_ts`), but the shipped node emits ONE `i64` field `bars_since_open`. Now states the real contract: the count of completed bar-periods since the local (tz-aware, DST-correct) session open, close-instant indexed (09:15->1, 09:45->3; pre-open <=0), with the Frankfurt 09:00 Europe/Berlin open + `period_minutes` knob + the value-ignored `trigger`. - authoring-guide: a "Session anchoring: the SessionFrankfurt preset" subsection — schema (trigger / period_minutes / bars_since_open), the close-instant multiples, the EqConst gating pattern, DST handling, and the add-snippet. Verified against `aura graph introspect --node SessionFrankfurt`. Declared-tap authoring (C27 / the #284 tap op): - authoring-guide: the `tap` op-table row (seven ops -> eight) + a worked "declaring a measurement tap" example. The example is verified: piping it through `aura graph build` emits `taps:[{"name":"spread","from":{"node":2,"field":0}}]`. - design ledger op-list (INDEX.md): a `tap` bullet (seven verbs -> eight); also corrected the `expose` "only verb that keeps `as`" claim — `tap` keeps it too. - README op-kind enumeration: adds `tap`. Docs only; no code change. Both worked examples were run through the built `aura` binary; the SessionFrankfurt schema was checked against introspect. closes #285
This commit is contained in:
@@ -118,8 +118,9 @@ aura graph build < crossover.ops.json > crossover.bp.json
|
||||
aura sweep crossover.bp.json --list-axes
|
||||
```
|
||||
|
||||
The op kinds are `source`, `input`, `add`, `feed`, `connect`, `expose`, and
|
||||
`gang`. See
|
||||
The op kinds are `source`, `input`, `add`, `feed`, `connect`, `expose`, `tap`,
|
||||
and `gang` (`tap` declares a recorded measurement point on an interior wire —
|
||||
see the authoring guide). See
|
||||
`aura graph introspect --node <T>` for a type's exact ports and the op-script
|
||||
grammar in the design ledger for the full semantics.
|
||||
|
||||
|
||||
+65
-2
@@ -217,7 +217,7 @@ $ aura graph build < smacross.json > blueprint.json
|
||||
Nodes are referenced by an **identifier** (given by `add`, see below); ports
|
||||
are dotted `<identifier>.<port>` on both sides of a wire.
|
||||
|
||||
### The seven ops
|
||||
### The eight ops
|
||||
|
||||
| op | JSON shape | does |
|
||||
|---|---|---|
|
||||
@@ -226,7 +226,8 @@ are dotted `<identifier>.<port>` on both sides of a wire.
|
||||
| `add` | `{"op":"add","type":<TypeId>,"name":<str>?,"bind":{<param>:<Scalar>}?}` | instantiate a node of a type in the closed vocabulary (`aura graph introspect --vocabulary`) — see §0 below for how a type gets into that vocabulary in the first place. `name` becomes the node's identifier for later ops (default: the type's own lowercase label — two unnamed nodes of the same type then collide). `bind` sets zero or more of its params. |
|
||||
| `feed` | `{"op":"feed","role":<str>,"into":[<port>, …]}` | fan a previously-declared role into one or more interior input slots, all-or-nothing (a failing target leaves none of the batch wired). |
|
||||
| `connect` | `{"op":"connect","from":<port>,"to":<port>}` | wire one interior output field to one interior input slot. A `connect` that would close a dataflow cycle is rejected immediately — the only legal feedback path is an explicit delay/state node (domain invariant 5). |
|
||||
| `expose` | `{"op":"expose","from":<port>,"as":<str>}` | promote an interior output field to a boundary output under the alias `as` — the only op whose name key is a real *alias* (contrast `add`'s `name`, which is an identifier, not a rename). |
|
||||
| `expose` | `{"op":"expose","from":<port>,"as":<str>}` | promote an interior output field to a boundary output under the alias `as` — a real *alias* (a terminal boundary name, not a referenceable identifier like `add`'s `name`). Together with `tap`, one of the two ops whose `as` key is a terminal name rather than an identifier. |
|
||||
| `tap` | `{"op":"tap","from":<port>,"as":<str>}` | declare a **measurement tap** on an interior output field under the name `as` — the output-side twin of `expose` (a recorded observation point, not a boundary output; a `Composite.taps` entry, C27). A single `aura run` constructs a recorder at each declared tap and persists its per-cycle series as a `ColumnarTrace`; a sweep leaves it inert. Tap names are their own namespace and must be unique (a second `tap` under one name refuses: `duplicate tap name`). |
|
||||
| `gang` | `{"op":"gang","as":"channel_length","into":["channel_hi.length","channel_lo.length"]}` | Fuse two or more sibling params into ONE public knob: the member addresses leave the sweepable param space and `as` replaces them; the bound or swept value fans out to every member at bootstrap. Members must share one scalar kind and stay open (un-bound). |
|
||||
|
||||
Value forms are the typed-tag representations used everywhere in this
|
||||
@@ -273,6 +274,68 @@ fused with its siblings under ONE public knob declared by a `gang` op; the
|
||||
member addresses are unbindable and only the gang's own single-segment name —
|
||||
wrapped like any knob, e.g. `graph.channel_length` — appears in `--list-axes`).
|
||||
|
||||
### Worked example: declaring a measurement tap
|
||||
|
||||
A **declared tap** (C27) names an interior output wire as a recorded
|
||||
observation point — the output-side twin of `expose`, but a *measurement*
|
||||
rather than a boundary output. It is authored by the same `"node.field"`
|
||||
addressing every other op uses; there is no raw node index. Extending the
|
||||
crossover above to record the raw fast−slow spread (the signal *before* the
|
||||
bias scaling) under the name `spread`:
|
||||
|
||||
```json
|
||||
[
|
||||
{"op": "source", "role": "price", "kind": "F64"},
|
||||
{"op": "add", "type": "SMA", "name": "fast"},
|
||||
{"op": "add", "type": "SMA", "name": "slow"},
|
||||
{"op": "feed", "role": "price", "into": ["fast.series", "slow.series"]},
|
||||
{"op": "add", "type": "Sub", "name": "sub"},
|
||||
{"op": "connect", "from": "fast.value", "to": "sub.lhs"},
|
||||
{"op": "connect", "from": "slow.value", "to": "sub.rhs"},
|
||||
{"op": "add", "type": "Bias", "name": "bias"},
|
||||
{"op": "connect", "from": "sub.value", "to": "bias.signal"},
|
||||
{"op": "tap", "from": "sub.value", "as": "spread"},
|
||||
{"op": "expose", "from": "bias.bias", "as": "bias"}
|
||||
]
|
||||
```
|
||||
|
||||
The built blueprint carries a `taps` array naming the resolved wire
|
||||
(`{"name":"spread","from":{"node":<sub's index>,"field":0}}` — the name is
|
||||
addressed, the index is resolved *for* you). A single `aura run <blueprint>`
|
||||
then constructs a recorder at that point and persists the `spread` series as a
|
||||
`ColumnarTrace` in the run's trace store, chartable by its name. A tap is inert
|
||||
in a sweep (no per-cell recorder) — it is a single-run observation surface.
|
||||
(`sub.value` is tapped here purely to illustrate; any interior output field is
|
||||
a legal tap source, and a producer needs no other consumer to be tapped.)
|
||||
|
||||
### Session anchoring: the `SessionFrankfurt` preset
|
||||
|
||||
To anchor logic to the trading session, the closed vocabulary ships a
|
||||
`SessionFrankfurt` node — the Frankfurt cash open (09:00 `Europe/Berlin`)
|
||||
baked in, DST-correct. Its schema:
|
||||
|
||||
- **input** `trigger: f64` — wired from a once-per-bar stream (e.g. a 15m
|
||||
resampler's `close`); its *value is ignored*, it only clocks the node to fire
|
||||
once per completed bar;
|
||||
- **param** `period_minutes: i64` — the bar width to index by (conventionally
|
||||
`15`, matching the resampler);
|
||||
- **output** `bars_since_open: i64` — the count of completed `period_minutes`
|
||||
bars since the local session open, in tz-aware local wall-clock time.
|
||||
|
||||
Because a resampler emits on exact `:00/:15/:30/:45` boundaries and `ctx.now()`
|
||||
at emission is the *just-closed* bar's close instant, in-session bar closes
|
||||
read exact positive multiples: the 09:00–09:15 bar closes at **09:15 → 1**,
|
||||
09:30–09:45 at **09:45 → 3**, 10:00–10:15 at **10:15 → 5**. Pre-open instants
|
||||
give `<= 0` (non-actionable). To gate on "the third bar of the session",
|
||||
compare `bars_since_open` against a constant (an `EqConst(==3)`); there is no
|
||||
separate in-session bool — `bars_since_open` alone is the contract. DST is
|
||||
handled by `chrono-tz`, so the same local minute reads the same index in
|
||||
summer (CEST) and winter (CET).
|
||||
|
||||
Wire it like any node —
|
||||
`{"op":"add","type":"SessionFrankfurt","bind":{"period_minutes":{"I64":15}}}`,
|
||||
feed its `trigger` from a once-per-bar stream, and read `bars_since_open`.
|
||||
|
||||
### Commands
|
||||
|
||||
```
|
||||
|
||||
+10
-3
@@ -2373,7 +2373,7 @@ fixture from the scaffolder.
|
||||
**Op-script grammar (`aura graph build`, the #157 wire surface).** The construction
|
||||
op-script is a JSON **array of ops**, each object internally tagged by `"op"`,
|
||||
replayed in order; nodes are referenced **by identifier**, ports as dotted
|
||||
`<identifier>.<port>`. The seven verbs:
|
||||
`<identifier>.<port>`. The eight verbs:
|
||||
- `source` — `{"op":"source","role":<str>,"kind":<ScalarKind>}` — declare a root
|
||||
source role producing a base column of `kind`.
|
||||
- `input` — `{"op":"input","role":<str>}` — declare a root input role (kind inferred
|
||||
@@ -2388,8 +2388,15 @@ replayed in order; nodes are referenced **by identifier**, ports as dotted
|
||||
field to an input slot; a `connect` closing a cycle is rejected eagerly
|
||||
(invariant 5).
|
||||
- `expose` — `{"op":"expose","from":<port>,"as":<str>}` — promote an interior output
|
||||
field to a boundary output, aliased by `as` — **the only verb that keeps `as`**
|
||||
(a real alias, not a naming; contrast `add`'s `name`).
|
||||
field to a boundary output, aliased by `as` (a real alias, not a naming; contrast
|
||||
`add`'s `name`) — one of the two verbs that keep `as` (with `tap`).
|
||||
- `tap` — `{"op":"tap","from":<port>,"as":<str>}` — declare a measurement **tap** on
|
||||
an interior output field under `as` (#284) — the output-side twin of `expose`: a
|
||||
recorded observation point (a `Composite.taps` entry, C27), not a boundary output.
|
||||
Name-addressed like every other op (no raw index); tap names are their own
|
||||
namespace (a duplicate refuses). The `finish` gate threads op-declared taps into
|
||||
the built `Composite` (`.with_taps`); a single `aura run` records each, a sweep
|
||||
leaves them inert.
|
||||
- `gang` — `{"op":"gang","as":"channel_length","into":["channel_hi.length","channel_lo.length"]}`
|
||||
— fuse two or more sibling params into ONE public knob: the member addresses
|
||||
leave the sweepable param space and `as` replaces them; the bound or swept
|
||||
|
||||
+1
-1
@@ -259,7 +259,7 @@ The four streamed scalar kinds — `i64`, `f64`, `bool`, `timestamp` (a newtype
|
||||
|
||||
### session node
|
||||
**Avoid:** session window
|
||||
A node that exposes session context as scalar streams (`bars_since_open`, `in_session`, `session_open_ts`) so session logic stays inside the stream model. Calendars and instrument specs remain metadata beside the hot path.
|
||||
A node that exposes session context as a scalar stream so session logic stays inside the stream model. The shipped `Session` / `SessionFrankfurt` node (`aura-std`) emits ONE `i64` field, `bars_since_open` — the count of completed bar-periods since the local (tz-aware, DST-correct) session open, indexed off the just-closed bar's close instant (in-session closes read exact positive multiples: 09:15→1, 09:45→3; pre-open ≤0). `SessionFrankfurt` bakes the Frankfurt 09:00 `Europe/Berlin` open with a single `period_minutes` knob; its `trigger` input only clocks it once per completed bar (the value is ignored). Calendars and instrument specs remain metadata beside the hot path.
|
||||
|
||||
### sign-agreement
|
||||
**Avoid:** sign consistency, market breadth
|
||||
|
||||
Reference in New Issue
Block a user