Skip to content

D8: declare indicators projected-but-unread and widen it to match what is produced - #28

Merged
Gio2050 merged 2 commits into
mainfrom
mission/d8-type-boundary-v0.1
Aug 4, 2026
Merged

D8: declare indicators projected-but-unread and widen it to match what is produced#28
Gio2050 merged 2 commits into
mainfrom
mission/d8-type-boundary-v0.1

Conversation

@Gio2050

@Gio2050 Gio2050 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Type-only correction to FroggyEnrichedView.technical.indicators, surfaced by afi-reactor's D8 pass (the reactor↔afi-core boundary is now genuinely typechecked instead of ambient any).

⚠️ Merge this PR FIRST. The paired afi-reactor PR cannot pass CI until this is on main — its CI checks out afi-core at the default branch with no ref:, and its new strict typecheck step fails against afi-core@main.

What and why

1. Documentation gap. indicators' siblings atr14 and trendBias carry explicit "context only — NOT read by this adapter or any scorer input" annotations, and atrRegime documents that it is read (AR-GOV D-AR-3). indicators carried no annotation at all, so a reader could not tell which it was.

It is read by nothing. The only mentions of the token anywhere in afi-core are this declaration and an unrelated indicators array on the universal signal schema. The adapter reads only emaDistancePct / isInValueSweetSpot / brokeEmaWithBody / atrRegime. This survived five independent refutation angles during triage.

2. The declared type did not describe what is produced. The reactor's projecting node writes volume_ratio from TechnicalLensV1.volumeRatio, which is optional — so the key is written with an undefined value. The Record value type was number | null, and a Record value position has no optionality escape hatch, which is why volume_ratio was the only one of the four indicators to error.

Why the fix lands here rather than in the reactor

This was the deciding constraint. Canonical JSON drops an undefined-valued key but retains a null-valued one:

  • ?? null in the reactor → changes the produced value. Would move hashes.
  • Building the key conditionally → changes Object.keys().
  • Widening the declared type → provably changes nothing.

Only the third is zero-behaviour-change. (Precise scope: this view reaches no live hash preimage today — enrichmentHash is taken over the raw lane payloads, not this renamed projection — but strategyLocalViewHash is a declared-but-unproduced pin over exactly this view, so a coercion would become hash-relevant the moment that producer is wired up.)

Verification

  • 19/19 emitted dist/*.js byte-identical before and after — proven type-only.
  • 231/231 tests green.
  • dist/ also cleaned: it held 3 artifacts (runtime/afiRuntimeAdapter.js, runtime/types.js, src/dag/SignalEnvelope.js) whose sources were deleted in Mission R, so it was not reproducible from source. That matters now that afi-reactor typechecks against this dist/ — stale declarations there could mask or invent errors.

Governance

No filing required. Type-only, nothing in afi-core reads indicators, and no scored value, hash, or golden byte moves. Recorded explicitly because it widens a published protocol-primitive contract consumed across repos.

Full report: reports/afi-d8-type-boundary-v0.1.md

Gio2050 and others added 2 commits August 4, 2026 04:50
…ch reality

Two type-only corrections to FroggyEnrichedView.technical.indicators, both
surfaced by afi-reactor's D8 pass (the reactor↔afi-core boundary is now
genuinely typechecked instead of ambient `any`).

1. Documentation gap. indicators' siblings atr14 and trendBias carry explicit
   "context only — NOT read by this adapter or any scorer input" annotations,
   and atrRegime documents that it IS read (AR-GOV D-AR-3). indicators carried
   no annotation at all, so a reader could not tell which it was. It is read by
   nothing: the only mentions of the token anywhere in afi-core are this
   declaration and an unrelated `indicators` array on the universal signal
   schema. Now annotated to match its siblings.

2. The declared type did not describe what is produced. The reactor's
   projecting node writes volume_ratio from TechnicalLensV1.volumeRatio, which
   is optional, so the key is written with an `undefined` value. The Record
   value type was `number | null`, which a Record position cannot widen away —
   the only field of the four to error.

The distinction is load-bearing and is why the fix lands here rather than in
the reactor: canonical JSON DROPS an undefined-valued key but RETAINS a
null-valued one. Coercing with `?? null` in the reactor would have moved
hashes; building the key conditionally would have changed Object.keys(). The
only zero-behaviour-change option is to make the declared type honest.

Type-only, proven: all 22 emitted dist/*.js files are byte-identical before
and after. 231/231 tests green.

Pairs with afi-reactor's D8 PR and must merge with it — the reactor now binds
to this package's published dist/*.d.ts rather than to an `any` shim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review correction. The comment justified widening with "a coercion that would
move hashes", which overstates the present situation: technical.indicators
reaches no live hash preimage today — enrichmentHash is computed over the raw
lane payloads and sorted categories, not over this renamed {rsi, ema_20,
ema_50, volume_ratio} projection.

The accurate justification, now recorded: widening is the only option that
provably changes nothing (coercing with `?? null` alters the value, omitting
the key alters Object.keys()), AND strategyLocalViewHash is a
declared-but-unproduced pin over exactly this view — so a coercion here would
silently become hash-relevant the moment that producer is wired up.

Still type-only: 19/19 emitted .js byte-identical (a clean build emits 19; the
previously-reported 22 counted three stale artifacts from sources deleted in
Mission R — dist/runtime/*.js and dist/src/dag/SignalEnvelope.js — which
`npm run build:clean` has now dropped). This matters beyond tidiness: afi-reactor
now typechecks against this dist/, so stale declarations here could mask or
invent errors across the D8 seam.

231/231 tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@kilo-code-bot

kilo-code-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

Kilo Code Review could not run — your account is out of credits.

Add credits or switch to a free model to enable reviews on this change.

@Gio2050
Gio2050 merged commit 5b43905 into main Aug 4, 2026
1 of 2 checks passed
@Gio2050
Gio2050 deleted the mission/d8-type-boundary-v0.1 branch August 4, 2026 18:22
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