Files
claude 14c43474ab fieldtest: construction-args — 4 examples, 0 bugs / 1 friction / 2 spec-gap / 4 working
Source-blind consumer run over the #271 surface: a NY first-bar-
momentum Session authored purely as data and run e2e over --real
US500; a LinComb{arity:3} blend binding the arity-unlocked weights
with the Session period swept (4 members, reproduced 4/4
bit-identically); eight strict-form refusal probes (all exit 1,
actionable prose); an args composite registered and use-spliced into
a consumer (v2 propagates, ids stable, C29 doc gate holds).

Findings routed: Count lexical form + asymmetry ratified into
glossary/guide (follow-up commit); introspect dead-end on arg-bearing
ports and the missing CostSum wiring path absorbed into #341.

refs #271
2026-07-24 22:05:34 +02:00

9.4 KiB
Raw Permalink Blame History

Fieldtest transcript — cycle #271 (typed construction args)

Verbatim command/output capture for the fieldtests/construction-args/ fixtures. Binary: target/debug/aura built from HEAD 6ca359a via cargo build --workspace (debug profile). Runs used a scratch project aura new ca_lab (in /tmp/ca_lab); real data resolved from the built-in archive (/mnt/tickdata/Pepperstone). All op-scripts authored from the public interface only (README, authoring guide, glossary, design ledger C24, aura graph introspect).

Public interface only — no crate source read.


Discovery loop (axis 3) — is the args grammar learnable from the binary?

$ aura graph introspect --vocabulary | wc -l
36

$ aura graph introspect --node Session
Session — bars elapsed since the session open, from the configured open time and timezone
  arg    tz: Tz   (IANA timezone name, e.g. Europe/Berlin)
  arg    open: TimeOfDay   (local wall-clock HH:MM)
  note   ports and params form at construction; args are required

$ aura graph introspect --node LinComb
LinComb — linear combination of its inputs with constant weights
  arg    arity: Count   (positive integer count)
  note   ports and params form at construction; args are required

$ aura graph introspect --node CostSum
CostSum — sums cost-model contributions into one cost-in-R stream
  arg    n_costs: Count   (positive integer count)
  note   ports and params form at construction; args are required

The arg grammar (name, kind, hint) is fully self-describing. But --node <T> stops at the note — it does NOT reveal the post-construction ports/params. To learn the port names you must author a partial op-list carrying the args and pipe it through --unwired; to learn the param names you must build and --list-axes. Nothing on the --node surface names those follow-up steps. First naive guess at LinComb's input port was lc.in0 — wrong:

$ echo '[{"op":"add","type":"LinComb","name":"lc","args":{"arity":"3"}}]' \
    | aura graph introspect --unwired
lc.term[0]:F64
lc.term[1]:F64
lc.term[2]:F64

$ echo '[{"op":"add","type":"CostSum","name":"cs","args":{"n_costs":"3"}}]' \
    | aura graph introspect --unwired
cs.cost[0].cost_in_r:F64
cs.cost[0].cum_cost_in_r:F64
cs.cost[0].open_cost_in_r:F64
cs.cost[1].cost_in_r:F64          (… ×3 slots, 3 fields each)

Weight param names, discovered only after a build:

$ aura graph build < lc_probe.ops.json > lc_probe.bp.json
$ aura sweep lc_probe.bp.json --list-axes
graph.lc.weights[0]:F64          # open by default → sweepable (C24)
graph.lc.weights[1]:F64
...

Example 1 — ca_1_ny_session (axis 1: non-Frankfurt Session as data)

$ aura graph build < ca_1_ny_session.ops.json > ca_1_ny_session.bp.json
$ head -c 25 ca_1_ny_session.bp.json
{"format_version":2,"blue          # arg-bearing → v2

$ aura graph introspect --content-id --identity-id < ca_1_ny_session.ops.json
5b085a5b3f411072e48d4b54717b3d09e88be50b0bd797d345c2ee90baa41c37   # content id
4bf0513a4d2e852ab580fd8846737f57c1825b944ff9df1a48040a07fe92446b   # identity id

# --- inside ca_lab/ ---
$ aura run ca_1_ny_session.bp.json --real US500 --from 1725148800000 --to 1725580800000
{"manifest":{... "topology_hash":"5b085a5b...b40d95cf"? -> 5b085a5b... (== content id) ...},
 "metrics":{... "expectancy_r":0.7853745294474523,"n_trades":18 ...}}    (exit 0)

# synthetic refuses cleanly (OHLC strategy, close-only synthetic stream):
$ aura run ca_1_ny_session.bp.json
aura: strategy "graph" consumes columns beyond close (open, high, low) —
      synthetic data generates a close series only; run with --real <SYMBOL>

Full report captured in ca_1_ny_session.run.json.


Example 2 — ca_2_lincomb_blend (axis 2: LinComb arity + weights + swept period)

Three SMA spreads blended through LinComb(arity 3) with bound weights[i], NY-session-gated, Session.period_minutes left OPEN (sweepable).

$ aura graph build < ca_2_lincomb_blend.ops.json > ca_2_lincomb_blend.bp.json
$ head -c 25 ca_2_lincomb_blend.bp.json
{"format_version":2,"blue

$ aura sweep ca_2_lincomb_blend.bp.json --list-axes
graph.sess.period_minutes:I64            # open
graph.lc.weights[0]:F64 default=0.5      # bound (re-openable by --axis)
graph.lc.weights[1]:F64 default=0.3
graph.lc.weights[2]:F64 default=0.2
...

# --- inside ca_lab/ --- sweep the arity-unlocked weight AND the period param:
$ aura sweep ca_2_lincomb_blend.bp.json --real US500 --from 1725148800000 --to 1725580800000 \
    --axis graph.sess.period_minutes=15,30 --axis 'graph.lc.weights[0]=0.3,0.5' --name ca2_blend
{"family_id":"caa3508a-0-US500-w0-r0-s0-0", ... 4 members ...}   (exit 0)

$ aura reproduce caa3508a-0-US500-w0-r0-s0-0
... member graph.lc.weights[0]=0.3, graph.sess.period_minutes=15 ... reproduced: bit-identical
... (×4)
reproduced 4/4 members bit-identically

Sweep output in ca_2_lincomb_blend.sweep.jsonl. NB: at this gating, the two weights[0] values yield identical member metrics (the gated blend's sign is weight-insensitive here) — a semantic property of the strategy, not the arg channel; the params bound and swept correctly.

CostSum arity probe: ca_2b_costsum_arity.ops.json--unwired shows cost[i].{cost_in_r,cum_cost_in_r,open_cost_in_r} (3 slots × 3 fields). Wiring CostSum end to end needs the RiskExecutor context (cost nodes take closed/open/entry_price/stop_price) — no op-script path for that exists in the public corpus (see finding SG2).


Example 3 — refusal battery (axis 4: strict-form refusal prose)

Every case: aura graph build < FIXTURE, stderr + exit code.

ca_3a_bad_tz         aura: op 1 (add): node sess arg "tz" expects Tz (IANA timezone name, e.g. Europe/Berlin) — got "berlin"        exit 1
ca_3b_unpadded_time  aura: op 1 (add): node sess arg "open" expects TimeOfDay (local wall-clock HH:MM) — got "9:30"                 exit 1
ca_3c_zero_count     aura: op 2 (add): node lc arg "arity" expects Count (positive integer count) — got "0"                        exit 1
ca_3d_garbage_count  aura: op 1 (add): node lc arg "arity" expects Count (positive integer count) — got "three"                    exit 1
ca_3e_missing_arg    aura: op 1 (add): node sess is missing required arg "tz"                                                       exit 1
ca_3f_args_on_argless aura: op 1 (add): node a takes no construction args                                                          exit 1
ca_3g_unknown_arg_key aura: op 1 (add): node sess has no construction arg "region"                                                 exit 1
ca_3h_partial_args    aura: op 1 (add): node sess is missing required arg "open"                                                    exit 1

Edge-form strictness (ad-hoc, --unwired):

Count  arity:  "3"→ok   "03"→refuse  "+3"→refuse  "3.0"→refuse  "1e1"→refuse  "-2"→refuse  " 3"→refuse  "0x3"→refuse
Time   open:   "09:30"→ok  "00:00"→ok  "23:59"→ok   "24:00"→refuse  "09:60"→refuse  "9:05"→refuse  "09:5"→refuse  "09:30:00"→refuse  "0930"→refuse

Note the padding asymmetry: open REQUIRES a zero-padded hour (09:05), but arity REFUSES a leading zero (03). See finding SG1.


Example 4 — ca_4 register → use (axis 5: #317 interplay)

$ aura graph build < ca_4_ny_anchor_pattern.ops.json > ca_4_ny_anchor_pattern.bp.json
$ head -c 25 ca_4_ny_anchor_pattern.bp.json
{"format_version":2,"blue
$ aura graph introspect --content-id --identity-id < ca_4_ny_anchor_pattern.ops.json
d51132463b2d1780b3ed9b83dc9f9a229a9d6b93fce3e15c7c0572970e2e4564
863c89ddeaa2dfe7d79c7e55944263145399c67397a27c0955bdd768cc6ed72e

# --- inside ca_lab/ ---
$ aura graph register .../ca_4_ny_anchor_pattern.bp.json --name ny_anchor
registered blueprint d51132463b2d... (.../runs/blueprints/d51132463b2d....json)
label "ny_anchor" -> d51132463b2d...

$ aura graph introspect --registered
ny_anchor  d51132463b2d  NY cash-open session anchor: bars elapsed since the 09:30 America/New_York open

$ aura graph build < ca_4_consumer.ops.json > consumer.bp.json
# stderr: aura: note: use "anchor": ny_anchor -> d51132463b2d...
$ head -c 25 consumer.bp.json
{"format_version":2,"blue          # v2 propagates THROUGH the splice
$ aura graph introspect --content-id --identity-id < ca_4_consumer.ops.json
985991f62cc6b92895018312c5ea86e493ef1c09dd53cf6e0813551baa97b671
bbe7060f4136255e594e1b01242ae80c3f5decfc543f23198d66adff6ce52a7b

$ aura run consumer.bp.json --real US500 --from 1725148800000 --to 1725580800000
{"manifest":{... "topology_hash":"985991f6..." (== content id),
   "defaults":[["graph.anchor.sess.period_minutes",{"I64":15}], ...] ...},
 "metrics":{... "n_trades":8 ...}}    (exit 0)

# doc gate on the args-bearing composite (doc stripped):
$ aura graph register ca4_docless.bp.json --name ny_docless
aura: blueprint: composite `graph` carries no doc — a registered composite
      describes itself (C29); add a doc line (...) before register       exit 1

Consumer report captured in ca_4_consumer.run.json.


Cross-cutting confirmations

# args-free document stays format_version 1 (byte-stability invariant, C18):
$ echo '[{"op":"source",...},{"op":"add","type":"SMA",...},...]' | aura graph build | head -c 25
{"format_version":1,"blue

# content id deterministic across builds:
$ aura graph introspect --content-id < ca_1_ny_session.ops.json   (×2) -> identical
5b085a5b3f411072e48d4b54717b3d09e88be50b0bd797d345c2ee90baa41c37
5b085a5b3f411072e48d4b54717b3d09e88be50b0bd797d345c2ee90baa41c37