Skip to content

feat: publish why a field has no reading: unavailable now, or undeclared for this radio - #3415

Merged
morozsm merged 6 commits into
mainfrom
codex/field-status-availability
Sep 9, 2026
Merged

feat: publish why a field has no reading: unavailable now, or undeclared for this radio#3415
morozsm merged 6 commits into
mainfrom
codex/field-status-availability

Conversation

@morozsm

@morozsm morozsm commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Coordinator-delegated file-count exception: 19 code + 0 regenerated baselines = 19 files.

Linear owner: MOR-2425. This PR delivers the T201 wire-availability slice recorded under that root; T201 does not have a separate Linear child issue.

Problem

The public fieldStatus wire contract previously collapsed three unobserved states into availability: "missing": a field the radio does not declare, a declared field unavailable in the current state, and a declared field that has not been observed yet. Presentation consumers therefore could not make the T201 distinction between structural presence and a reading.

Result

  • FieldStatusPublic.availability and its generated TypeScript union now include unavailable and undeclared.
  • Snapshot projection combines profile field policies with capability paths not marked unavailable to determine what the radio declares. available_when resolving to False or None emits unavailable; a projected path outside the declared set emits undeclared; an unconditional declared path that has not been observed remains missing.
  • Observed values keep the existing available or stale result until the freshness service discards them. Callers without profile acquisition metadata retain the previous all-missing fallback.
  • WebServer passes the profile-derived declaration and availability inputs into the projection.
  • isFieldRead centralizes the compatibility rule that resolved available and stale values remain admissible while missing, unavailable, and undeclared are rejected. It preserves getFieldAvailability's legacy no-entry fallback and does not establish observation evidence. It updates five legacy missing-only gates: Mode panel active-field visibility and MOD-input presence, automatic MOD-input derivation and dispatch, and the TX MOD-input warning source. This prevents the two new absence values from making controls visible or dispatch-admissible.
  • Tests cover real-profile missing/unavailable/undeclared states, schema conformance, no-profile compatibility, freshness discard, server wiring, inherited frontend status, and all five legacy gates.

The 19-file exception keeps one compatibility unit together: emitting the new wire values without converting the five legacy gates would change control visibility and dispatch authority. The PR remains below the 1000 changed-line ceiling at 555 additions and 74 deletions (629 total), measured with git diff --numstat origin/main...HEAD after the review wording correction.

Recovery and integration provenance

The existing public branch head was feedc9a77127c2f8817fad9a1a5e1e59e0f854aa. A clean unpublished Mini commit was recovered locally as 531d760537baa943873bbbeb5895c0196935aa6f, a direct descendant that supplies the five-gate compatibility correction and associated tests/docs. The integration branch preserves both commits and uses a normal merge of freshly fetched origin/main (b43d11443d052920072a5f4987e179c763b0b95f); no rebase or force push was used.

Focused verification

  • uv run pytest tests/test_web_runtime_helpers.py tests/web/test_state_schema_conformance.py tests/test_field_status_frontend_fixture.py -q --tb=short --timeout=300 --timeout-method=thread — 94 passed.
  • uv run pytest tests/test_web_server_coverage.py -k 'empty_state_store or partial_state_store' -q --tb=short --timeout=300 --timeout-method=thread — 2 passed, 153 deselected.
  • uv run python scripts/gen_state_types.py --check — generated type block is current.
  • uv run python scripts/gen_field_status_fixture.py --check — fixture is current and unchanged for the no-profile fallback.
  • cd frontend && npx --no-install vitest run src/lib/state/__tests__/field-status.test.ts src/lib/runtime/props/__tests__/panel-props.test.ts src/lib/runtime/adapters/__tests__/mod-input-auto.isolated.test.ts src/lib/runtime/adapters/__tests__/mod-input-tx-guard.isolated.test.ts — 4 files, 142 tests passed.
  • git diff --check origin/main...HEAD — clean.

The behavior checks above ran on aff969eb0b71e186b1484de0a97956f647d45c7d. Review then required a wording-only correction: document that either projection input can independently emit its corresponding absence and describe isFieldRead as a compatibility predicate rather than observation proof. The generated type check and git diff --check were rerun after that correction; runtime code did not change.

The full suite was intentionally not run in this recovery lease; required CI runs on the published exact head.

Follow-up boundary

This PR publishes wire truth and preserves the existing control gates. It does not implement the meter/scan structural consumer. That follow-up remains in draft PR #3414 and must be revised to consume availability without collapsing unavailable, missing, and undeclared. Reconnect/store/DOM work, presentation expansion, and hardware/radio acceptance are also outside this PR and were not completed here.

morozsm and others added 2 commits September 9, 2026 10:11
…red for this radio

`FieldStatusPublic.availability` gains `unavailable` (the profile declares
the field and its `available_when` clauses do not hold in this state) and
`undeclared` (the profile declares no acquisition entry for the path).
`missing` keeps its old meaning: declared, admitted here, not yet observed.

The projection takes the two new inputs as optional keyword arguments, so
every caller that passes neither is unchanged. `WebServer._build_public_
state_from_snapshot` derives them from `self._get_profile().
state_acquisition` through `runtime_helpers.snapshot_field_status_inputs`,
which is also what the tests call.

An OBSERVED field keeps `available`/`stale` even where its clauses now read
False: `StateFreshnessService._discard_declared_absent` removes it, and only
then does the projection seed the absence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…on doc enumerates the set

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@morozsm
morozsm marked this pull request as ready for review September 9, 2026 14:13
morozsm and others added 2 commits September 9, 2026 10:47
… and re-pin what the change falsified

`FieldStatusPublic.availability` now carries `unavailable` and `undeclared`
alongside `missing`. Five frontend gates compared against `'missing'` alone
and so flipped from hidden to shown under either new value:

- `props/panel-props.ts: activeFieldShown` (rfGain/squelch/att/preamp);
- `props/panel-props.ts: toModeProps.hasModInput`;
- `adapters/mod-input-auto.svelte.ts: deriveAutoLanModInputProps`;
- `adapters/mod-input-auto.svelte.ts: autoSetLanModInputForTx`;
- `adapters/mod-input-tx-guard.svelte.ts: offendingSource`.

All five mean "the backend has read it", so they now call one helper,
`state/field-status.ts: isFieldRead` (`available` or `stale`). Each site has
a case pinning hidden under both new values; mutating each site back to its
old comparison reddens exactly those cases.

`tests/test_web_server_coverage.py`: the two IC-7610 tests asserting
`powerOn` is `missing` are re-pinned to `undeclared` and renamed — the
profile names `global.tx_state.power_on` in neither `field_policies` nor
`capabilities`.

Prose corrected where it stated `missing` as the only absence, or named the
test this branch renamed: `acquisition_scheduler.py:
StateFreshnessService._discard_declared_absent`, `state_store.py:
StateStore.discard`, three comments in `web/runtime_helpers.py`,
`docs/architecture/field-path-promotion-criterion.md`,
`docs/internals/radio-state-pipeline-validation.md` (the row's covering
tests and the definition of `undeclared`), and
`frontend/src/lib/state/field-status.ts`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@morozsm

morozsm commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: BLOCKED aff969e

Independent whole-candidate review: runtime changes and prior B1–B5 corrections pass the focused checks below. Two newly added/touched documentation claims remain false; these require wording corrections, not behavior changes (repository CLAUDE.md prose-accuracy rule).

  • P2: frontend/src/lib/state/field-status.ts:79-84 presents isFieldRead as backend observation proof. It delegates to getFieldAvailability, whose intentional no-entry/no-ancestor compatibility fallback returns available. Independently, isFieldRead({fieldStatus:{}}, 'main.rfGain') returns true with no observation; an explicit available status with observed:false also passes. Keep that legacy behavior. Describe the helper as the resolved available/stale compatibility gate and remove the observation guarantee. Apply the same correction to the touched caller claims in runtime/props/panel-props.ts:568-571, runtime/adapters/mod-input-tx-guard.svelte.ts:16-17, and the PR-body helper bullet. Risk: future consumers treat this predicate as observed-state/command evidence when it is not.
  • P2: src/rigplane/web/state_schema.py:194-196 says both projection arguments are required. They are independently optional: a call supplying only availability={power:False} emits unavailable; one supplying only declared=frozenset() emits undeclared. Both cases were reproduced through build_public_state_payload_from_snapshot. Remove that inaccurate dependency statement and regenerate frontend/src/lib/types/state.ts, which copies the docstring. No API change is requested.

Independent evidence:94 focused projection/schema/fixture Python tests plus2 B1 server-coverage cases PASS;142 committed frontend cases plus5 separate helper compatibility cases PASS. Reverted each of the five B2 sites individually in the reviewer worktree: M1–M5 each produced exactly2 expected assertion failures, then source bytes were restored. Eight real-profile empty-snapshot projections preserve all non-status raw/default fields and never manufacture observed evidence; IC705 declaration union and TX500 no-profile fallback confirmed. False/None gating, observed-value retention and freshness discard are covered by the passing projection cases. Both generators are clean. No full suite or hardware run; reviewer tree restored clean.

CI as found: quick running, visual successful, Agent Review Gate awaiting a valid final verdict. After the prose-only correction, review only that delta and generator result; the source/mutation review need not be repeated. The coordinator-approved19-file exception is present. Meter/scan structural follow-up remains outside this PR.

@morozsm

morozsm commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: BLOCKED 2414855

Delta-only review: the two previous documentation classes and PR-body claim are corrected, with no runtime changes. One replacement sentence reverses the condition: frontend/src/lib/runtime/props/panel-props.ts:568-569 says “The control is hidden while isFieldRead admits its resolved availability.” The actual hasModInput expression admits the control when that predicate is true (subject to its existing capability/state guards).

Required: delete that clause, or describe the control as using isFieldRead for availability compatibility. Preserve behavior. No repeat of the96 Python/147 frontend checks or M1–M5 mutations is needed; the next review is only this wording delta. CI as found: quick/visual and contract/type gates running. Prior source review evidence remains valid.

@morozsm

morozsm commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: PASS a78e2c9

Final delta-only review: the inverted panel-props comment is corrected. It now accurately describes isFieldRead as an availability compatibility gate, preserving the legacy no-entry fallback without claiming observation evidence. The prior optional-input schema/generated-doc correction also stands. Runtime behavior is unchanged from independently reviewed aff969e; no source or mutation tests were repeated for these wording-only deltas.

Prior independent evidence:96 Python and147 frontend checks PASS; reverting each of the five compatibility gates separately caused exactly2 expected failures; eight-profile projection/raw-payload compatibility and both generators verified. The two review finding classes are resolved. Meter/scan consumers and hardware acceptance remain separate.

REQUIRED BEFORE MERGE — PR body only: its current census still says554 additions/73 deletions=627. The final diff is555 additions/74 deletions=629 across19 files. Update that sentence; the approved19 code+0 regenerated baselines exception remains accurate. No source commit is needed.

CI as found: fresh visual and consumer/type/grep gates running; quick was not yet listed. Exact-head required-check completion remains coordinator-owned. Head rechecked immediately before publication.

@morozsm

morozsm commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Required before merge correction completed: the PR body now reports the exact final diff as 19 files, 555 additions, 74 deletions, 629 changed lines at a78e2c9. GitHub body readback confirms the old 554/73/627 count is absent. No source push was needed for this body-only correction.

@morozsm
morozsm merged commit 44ed3e3 into main Sep 9, 2026
17 of 18 checks passed
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