Skip to content

Common Job spec reporting via Beholder - #23227

Draft
skudasov wants to merge 3 commits into
developfrom
RANE-4655-common-job-spec-metrics
Draft

Common Job spec reporting via Beholder#23227
skudasov wants to merge 3 commits into
developfrom
RANE-4655-common-job-spec-metrics

Conversation

@skudasov

@skudasov skudasov commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Emits every job the node runs as CLJobInfo telemetry on Beholder — job identity, Job
Distributor provenance, and the complete definition as TOML

Requires

What changed

Revised after review feedback and the 2026-09-02 sync. The previous revision added a new
core/services/cljobinfo package and a second service in application.go. This revision
folds the generic reporting into the existing core/services/nodestatusreporter/jobspec
service, which already does this for one job type.

One service, one job.Listener registration, one poll loop, two emission tracks:

Track Scope Status
CLJobInfo (new) every job, any type; full definition as TOML generic
JobSpecEvent (existing) OCR2 only, filtered by EnabledOCR2PluginTypes unchanged

A failure on one track never suppresses the other.

application.go is untouched — the service is already wired and already receives
csaPubKeyHex, static.Version, hostname and feedsORM.

Why here and not in feeds/service.go

The review asked to emit this from where we already hook into JD and sync node info
(feeds/service.go, next to SyncNodeInfo), reusing existing types.

Agreed on the principle — no third parallel job-telemetry utility. But that specific
location doesn't work:

  • Import cycle. nodestatusreporter/jobspec imports feeds for feeds.ORM, so the
    feeds service cannot call the reporter unless the reporter moves into feeds.
  • Feature-flag gap. feedsService is a NullService when Feature.FeedsManager is
    off, so telemetry would silently stop on those nodes.
  • Coverage. Feeds-side hooks fire only for JD-driven changes (ApproveSpec,
    CancelSpec, DeleteJob). Jobs created via CLI, UI or TOML on disk would be invisible
    unless the spawner listener is kept anyway — which makes the hooks redundant.
  • New dependency. feeds has no Beholder dependency today.

nodestatusreporter/jobspec is already "the place where we do this": same Beholder
emitter, same job.Listener create/delete triggers, same ActiveJobs() sweep, same node
identity fields, and it already reads feeds.ORM for job-proposal lifecycle. This PR
generalises it rather than adding a sibling.

On the "call the sync hook whenever changes are made" pattern: jobs already have an event
bus, job.Spawner.RegisterListener, which fires for both JD-driven and non-JD changes.
syncNodeInfoWithRetry exists because chain configs have no such event and because the
wsrpc destination can be disconnected; Beholder is fire-and-forget, so there is no
connection to retry against and nothing equivalent to add.

Proto reuse

Not available. Checked orchestrator/feedsmanager, job-distributor/v1/job and
job-distributor/v1/node — none models a job's contents. JD carries the spec as an
opaque TOML string (ProposeJobRequest.spec, Proposal.spec), so spec_toml matches
JD's own representation rather than inventing a new one.

What we do reuse is the join key. New optional fields — feeds_manager_id,
remote_uuid, spec_version, proposed_at, approved_at — carry JD provenance, with
remote_uuid linking a CLJobInfo back to api.job.v1.Job.uuid. They are unset for jobs
that did not arrive via a job proposal, which is how a consumer distinguishes a JD-managed
job from a directly-created one.

Behaviour and config

  • Gated by the existing [JobSpecReporter] Enabled (default false) and
    PollingInterval (default 1h). No config schema change.
  • EnabledOCR2PluginTypes continues to gate the legacy OCR2 track only; the generic track
    ignores it by design.
  • No change for existing JobSpecEvent consumers.

Known gap

In ApproveSpec, jobSpawner.CreateJob runs inside the transaction, before
orm.ApproveSpec commits — so the CREATE event for a freshly approved job can lack JD
provenance until the next heartbeat fills it in. Fixing this properly means moving the
spawner call out of the transaction; out of scope here.

Follow-up

Once SOT confirms CLJobInfo covers what the existing data-feeds consumers need,
ShouldEmit, EmitForJob, buildEvent and the events package can be deleted in
place
— no wiring or config changes needed.

@github-actions

Copy link
Copy Markdown
Contributor

✅ No conflicts with other open PRs targeting develop

@github-actions

Copy link
Copy Markdown
Contributor

I see you updated files related to core. Please run make gocs in the root directory to add a changeset as well as in the text include at least one of the following tags:

  • #added For any new functionality added.
  • #breaking_change For any functionality that requires manual action for the node to boot.
  • #bugfix For bug fixes.
  • #changed For any change to the existing functionality.
  • #db_update For any feature that introduces updates to database schema.
  • #deprecation_notice For any upcoming deprecation functionality.
  • #internal For changesets that need to be excluded from the final changelog.
  • #nops For any feature that is NOP facing and needs to be in the official Release Notes for the release.
  • #removed For any functionality/config that is removed.
  • #updated For any functionality that is updated.
  • #wip For any change that is not ready yet and external communication about it should be held off till it is feature complete.

@cl-sonarqube-production

Copy link
Copy Markdown

@trunk-io

trunk-io Bot commented Jul 24, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

… reporter

Fold core/services/cljobinfo into the existing nodestatusreporter/jobspec
service instead of adding a parallel reporter. CLJobInfo is now emitted for
every job regardless of type, alongside the unchanged OCR2-only JobSpecEvent
track. application.go and the [JobSpecReporter] config are unchanged.

Requires: smartcontractkit/chainlink-protos#432
RANE-4655
Comment on lines +49 to +50
ProposedAt time.Time
ApprovedAt time.Time

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.

Is there any risk/ambiguity with using time.Time as opposed to a unix ms int64, for example?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

updated to account for this risk, but used google.protobuf.Timestamp
chainlink-protos changes: smartcontractkit/chainlink-protos@6acc8ef
chainlink changes: a66cc6d

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.

3 participants