Skip to content

fix(compat): D-003 exempts metric-bound widgets from the fields the binding fills (#3186) - #3230

Merged
vybe merged 1 commit into
devfrom
fix/3186-d003-metric-binding
Oct 5, 2026
Merged

vybe merged 1 commit into
devfrom
fix/3186-d003-metric-binding

Conversation

@dolho

@dolho dolho commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Summary

A metric, status or progress widget bound to a declared metric (metric: <name>) gets its value, and a status widget its colour, from the recorded points on every read (ent#479). The agent server's validate_widget already exempts bound widgets from those fields. Compatibility check D-003 (HARD) still required them, so an agent that followed the documented binding contract was reported incompatible.

  • D-003 now uses the agent server's predicate: a truthy metric. It skips exactly the fields the binding fills (_BOUND_FILLED): value for metric and progress widgets, and value + color for status widgets. The two rules reference each other so they stay in step.
  • label is still required, and unbound widgets keep today's rule.
  • AC 4 decision: a binding to an undeclared name is exempt too. For that case the backend renders "not declared" and drops any written value, so requiring one would mean writing a number nobody sees.
  • One correction to the issue: D-009 checks only the shape of the metrics: block. Nothing currently flags a binding to an undeclared name, and that would be a new check, so it's a possible follow-up rather than part of this PR.
  • progress is included even though the issue names only metric and status. The agent server already exempts it, and leaving it out would keep the two rules out of step.

Changes

  • src/backend/services/compatibility/static_checks.py: _BOUND_FILLED and the exemption in c_d003, with a docstring
  • docker/base-image/agent_server/routers/dashboard.py: a comment linking to the D-003 rule (no behaviour change)
  • docs/agent-validation-spec.md: the binding rule under D-003
  • tests/unit/test_compatibility_checks.py: 10 new cases

Test Plan

  • Bound widgets pass for all three types; unbound widgets still fail on each missing field; an empty, null or 0 binding is not a binding; a bound widget still needs its label; an undeclared binding passes. The four bound cases fail without the fix.
  • test_compatibility_checks.py + test_ent479_binding_and_compat.py + test_2110_widget_type_parity.py: 201 passed.

Fixes #3186

🤖 Generated with Claude Code

…inding fills (#3186)

A metric/status/progress widget bound to a declared metric gets its value
(and a status widget its color) from the recorded points on every read
(ent#479), and the agent server's validate_widget already exempts them.
D-003 still demanded value/color, so an agent following the documented
binding contract was reported HARD-incompatible.

D-003 now applies the agent server's predicate (a truthy `metric`) and
skips exactly the filled fields (_BOUND_FILLED); `label` stays required
and unbound widgets keep today's rule. Decided: a binding to an
undeclared name is exempt too — the backend renders "not declared" and
drops any written value, so requiring one demands a number nobody sees.
D-009 checks the metrics block's shape only; no check flags undeclared
bindings yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vybe

vybe commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

merge-train (2026-10-05, evening run): on the train (#3251). Nothing was pushed to this branch.

Follow-ups from validation, none blocking:

  1. No parity guard between the two rule sites. static_checks.py:1172 (_BOUND_FILLED) and agent_server/routers/dashboard.py:51 agree today (192-case differential, 0 mismatches), but only comments link them. Dropping the bound exemption for progress on the agent-server side, or changing its predicate from truthy to key-present, leaves the suite green. A parity test in the shape of test_2110_widget_type_parity.py would pin it.
  2. Older base images. An agent on an image built before feat(metrics): the one read — GET /metrics re-backed by metric_points, one stale rule, get_metrics, declared-metric tiles, metric: widget binding, D-010 (abilityai/trinity-enterprise#479) #2951 (2026-09-22) still strips a bound widget with no value, and D-003 now passes it. docs/agent-validation-spec.md:463 says a bound widget "needs only its label" with no caveat, while docs/user-docs/advanced/dynamic-dashboards.md:105 and docs/TRINITY_COMPATIBLE_AGENT_GUIDE.md:1895 still tell authors to keep a placeholder.
  3. Pre-existing since feat(metrics): the one read — GET /metrics re-backed by metric_points, one stale rule, get_metrics, declared-metric tiles, metric: widget binding, D-010 (abilityai/trinity-enterprise#479) #2951: a list or dict metric: value raises TypeError at metric_read_service.py:956, outside the try. Both validators treat it as a binding.

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge-train: batch validated on train/20261005-1731 (#3251)

@vybe
vybe merged commit cf88a1e into dev Oct 5, 2026
24 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.

2 participants