From 6234c1a49e3afe612ee6d9dbb945b4384ed40ad8 Mon Sep 17 00:00:00 2001 From: skudasov Date: Fri, 24 Jul 2026 12:53:06 +0200 Subject: [PATCH 1/6] wip --- node-platform/chip-schemas.json | 4 + node-platform/common/v1/cl_job_info.pb.go | 330 ++++++++++++++++++++++ node-platform/common/v1/cl_job_info.proto | 47 +++ 3 files changed, 381 insertions(+) create mode 100644 node-platform/common/v1/cl_job_info.pb.go create mode 100644 node-platform/common/v1/cl_job_info.proto diff --git a/node-platform/chip-schemas.json b/node-platform/chip-schemas.json index 9b555b51..024c325d 100644 --- a/node-platform/chip-schemas.json +++ b/node-platform/chip-schemas.json @@ -13,6 +13,10 @@ "entity": "common.v1.NodeJobInfo", "path": "common/v1/node_job_info.proto" }, + { + "entity": "common.v1.CLJobInfo", + "path": "common/v1/cl_job_info.proto" + }, { "entity": "common.v1.HealthInfo", "path": "common/v1/health_info.proto" diff --git a/node-platform/common/v1/cl_job_info.pb.go b/node-platform/common/v1/cl_job_info.pb.go new file mode 100644 index 00000000..a2c8abc2 --- /dev/null +++ b/node-platform/common/v1/cl_job_info.pb.go @@ -0,0 +1,330 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: node-platform/common/v1/cl_job_info.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// CLJobInfoTrigger is the reason a CLJobInfo event was emitted. +type CLJobInfoTrigger int32 + +const ( + CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_UNSPECIFIED CLJobInfoTrigger = 0 + CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_HEARTBEAT CLJobInfoTrigger = 1 + CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_CREATE CLJobInfoTrigger = 2 + CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_DELETE CLJobInfoTrigger = 3 +) + +// Enum value maps for CLJobInfoTrigger. +var ( + CLJobInfoTrigger_name = map[int32]string{ + 0: "CL_JOB_INFO_TRIGGER_UNSPECIFIED", + 1: "CL_JOB_INFO_TRIGGER_HEARTBEAT", + 2: "CL_JOB_INFO_TRIGGER_CREATE", + 3: "CL_JOB_INFO_TRIGGER_DELETE", + } + CLJobInfoTrigger_value = map[string]int32{ + "CL_JOB_INFO_TRIGGER_UNSPECIFIED": 0, + "CL_JOB_INFO_TRIGGER_HEARTBEAT": 1, + "CL_JOB_INFO_TRIGGER_CREATE": 2, + "CL_JOB_INFO_TRIGGER_DELETE": 3, + } +) + +func (x CLJobInfoTrigger) Enum() *CLJobInfoTrigger { + p := new(CLJobInfoTrigger) + *p = x + return p +} + +func (x CLJobInfoTrigger) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CLJobInfoTrigger) Descriptor() protoreflect.EnumDescriptor { + return file_node_platform_common_v1_cl_job_info_proto_enumTypes[0].Descriptor() +} + +func (CLJobInfoTrigger) Type() protoreflect.EnumType { + return &file_node_platform_common_v1_cl_job_info_proto_enumTypes[0] +} + +func (x CLJobInfoTrigger) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CLJobInfoTrigger.Descriptor instead. +func (CLJobInfoTrigger) EnumDescriptor() ([]byte, []int) { + return file_node_platform_common_v1_cl_job_info_proto_rawDescGZIP(), []int{0} +} + +// CLJobInfo is a generic, type-agnostic snapshot of a single Chainlink job. +// +// Unlike NodeJobInfo (which projects a few specific submitter/transmitter +// addresses across all jobs into a flat schema), CLJobInfo carries the job's +// common identity fields plus the complete job definition as a raw TOML string. +// This lets any job type be reported through a single schema without a +// dedicated proto message or extractor per job type. +type CLJobInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Node identity. + CsaPublicKey string `protobuf:"bytes,1,opt,name=csa_public_key,json=csaPublicKey,proto3" json:"csa_public_key,omitempty"` + NodeVersion string `protobuf:"bytes,2,opt,name=node_version,json=nodeVersion,proto3" json:"node_version,omitempty"` + Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"` + // Job identity — fields common to every job type (they live on job.Job + // itself rather than a type-specific spec). + ExternalJobId string `protobuf:"bytes,10,opt,name=external_job_id,json=externalJobId,proto3" json:"external_job_id,omitempty"` + JobId int32 `protobuf:"varint,11,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Name string `protobuf:"bytes,12,opt,name=name,proto3" json:"name,omitempty"` + JobType string `protobuf:"bytes,13,opt,name=job_type,json=jobType,proto3" json:"job_type,omitempty"` + SchemaVersion uint32 `protobuf:"varint,14,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` + ForwardingAllowed bool `protobuf:"varint,15,opt,name=forwarding_allowed,json=forwardingAllowed,proto3" json:"forwarding_allowed,omitempty"` + GasLimit *uint32 `protobuf:"varint,16,opt,name=gas_limit,json=gasLimit,proto3,oneof" json:"gas_limit,omitempty"` + StreamId *uint32 `protobuf:"varint,17,opt,name=stream_id,json=streamId,proto3,oneof" json:"stream_id,omitempty"` + CreatedAt string `protobuf:"bytes,18,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + // Complete job definition serialized as TOML. Captures all type-specific + // spec fields for any job type without requiring a per-type schema. + SpecToml string `protobuf:"bytes,30,opt,name=spec_toml,json=specToml,proto3" json:"spec_toml,omitempty"` + // Event metadata. + Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` + Timestamp string `protobuf:"bytes,41,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CLJobInfo) Reset() { + *x = CLJobInfo{} + mi := &file_node_platform_common_v1_cl_job_info_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CLJobInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CLJobInfo) ProtoMessage() {} + +func (x *CLJobInfo) ProtoReflect() protoreflect.Message { + mi := &file_node_platform_common_v1_cl_job_info_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CLJobInfo.ProtoReflect.Descriptor instead. +func (*CLJobInfo) Descriptor() ([]byte, []int) { + return file_node_platform_common_v1_cl_job_info_proto_rawDescGZIP(), []int{0} +} + +func (x *CLJobInfo) GetCsaPublicKey() string { + if x != nil { + return x.CsaPublicKey + } + return "" +} + +func (x *CLJobInfo) GetNodeVersion() string { + if x != nil { + return x.NodeVersion + } + return "" +} + +func (x *CLJobInfo) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *CLJobInfo) GetExternalJobId() string { + if x != nil { + return x.ExternalJobId + } + return "" +} + +func (x *CLJobInfo) GetJobId() int32 { + if x != nil { + return x.JobId + } + return 0 +} + +func (x *CLJobInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CLJobInfo) GetJobType() string { + if x != nil { + return x.JobType + } + return "" +} + +func (x *CLJobInfo) GetSchemaVersion() uint32 { + if x != nil { + return x.SchemaVersion + } + return 0 +} + +func (x *CLJobInfo) GetForwardingAllowed() bool { + if x != nil { + return x.ForwardingAllowed + } + return false +} + +func (x *CLJobInfo) GetGasLimit() uint32 { + if x != nil && x.GasLimit != nil { + return *x.GasLimit + } + return 0 +} + +func (x *CLJobInfo) GetStreamId() uint32 { + if x != nil && x.StreamId != nil { + return *x.StreamId + } + return 0 +} + +func (x *CLJobInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *CLJobInfo) GetSpecToml() string { + if x != nil { + return x.SpecToml + } + return "" +} + +func (x *CLJobInfo) GetTrigger() CLJobInfoTrigger { + if x != nil { + return x.Trigger + } + return CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_UNSPECIFIED +} + +func (x *CLJobInfo) GetTimestamp() string { + if x != nil { + return x.Timestamp + } + return "" +} + +var File_node_platform_common_v1_cl_job_info_proto protoreflect.FileDescriptor + +const file_node_platform_common_v1_cl_job_info_proto_rawDesc = "" + + "\n" + + ")node-platform/common/v1/cl_job_info.proto\x12\tcommon.v1\"\xa5\x04\n" + + "\tCLJobInfo\x12$\n" + + "\x0ecsa_public_key\x18\x01 \x01(\tR\fcsaPublicKey\x12!\n" + + "\fnode_version\x18\x02 \x01(\tR\vnodeVersion\x12\x1a\n" + + "\bhostname\x18\x03 \x01(\tR\bhostname\x12&\n" + + "\x0fexternal_job_id\x18\n" + + " \x01(\tR\rexternalJobId\x12\x15\n" + + "\x06job_id\x18\v \x01(\x05R\x05jobId\x12\x12\n" + + "\x04name\x18\f \x01(\tR\x04name\x12\x19\n" + + "\bjob_type\x18\r \x01(\tR\ajobType\x12%\n" + + "\x0eschema_version\x18\x0e \x01(\rR\rschemaVersion\x12-\n" + + "\x12forwarding_allowed\x18\x0f \x01(\bR\x11forwardingAllowed\x12 \n" + + "\tgas_limit\x18\x10 \x01(\rH\x00R\bgasLimit\x88\x01\x01\x12 \n" + + "\tstream_id\x18\x11 \x01(\rH\x01R\bstreamId\x88\x01\x01\x12\x1d\n" + + "\n" + + "created_at\x18\x12 \x01(\tR\tcreatedAt\x12\x1b\n" + + "\tspec_toml\x18\x1e \x01(\tR\bspecToml\x125\n" + + "\atrigger\x18( \x01(\x0e2\x1b.common.v1.CLJobInfoTriggerR\atrigger\x12\x1c\n" + + "\ttimestamp\x18) \x01(\tR\ttimestampB\f\n" + + "\n" + + "_gas_limitB\f\n" + + "\n" + + "_stream_id*\x9a\x01\n" + + "\x10CLJobInfoTrigger\x12#\n" + + "\x1fCL_JOB_INFO_TRIGGER_UNSPECIFIED\x10\x00\x12!\n" + + "\x1dCL_JOB_INFO_TRIGGER_HEARTBEAT\x10\x01\x12\x1e\n" + + "\x1aCL_JOB_INFO_TRIGGER_CREATE\x10\x02\x12\x1e\n" + + "\x1aCL_JOB_INFO_TRIGGER_DELETE\x10\x03BFZDgithub.com/smartcontractkit/chainlink-protos/node-platform/common/v1b\x06proto3" + +var ( + file_node_platform_common_v1_cl_job_info_proto_rawDescOnce sync.Once + file_node_platform_common_v1_cl_job_info_proto_rawDescData []byte +) + +func file_node_platform_common_v1_cl_job_info_proto_rawDescGZIP() []byte { + file_node_platform_common_v1_cl_job_info_proto_rawDescOnce.Do(func() { + file_node_platform_common_v1_cl_job_info_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_node_platform_common_v1_cl_job_info_proto_rawDesc), len(file_node_platform_common_v1_cl_job_info_proto_rawDesc))) + }) + return file_node_platform_common_v1_cl_job_info_proto_rawDescData +} + +var file_node_platform_common_v1_cl_job_info_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_node_platform_common_v1_cl_job_info_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_node_platform_common_v1_cl_job_info_proto_goTypes = []any{ + (CLJobInfoTrigger)(0), // 0: common.v1.CLJobInfoTrigger + (*CLJobInfo)(nil), // 1: common.v1.CLJobInfo +} +var file_node_platform_common_v1_cl_job_info_proto_depIdxs = []int32{ + 0, // 0: common.v1.CLJobInfo.trigger:type_name -> common.v1.CLJobInfoTrigger + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_node_platform_common_v1_cl_job_info_proto_init() } +func file_node_platform_common_v1_cl_job_info_proto_init() { + if File_node_platform_common_v1_cl_job_info_proto != nil { + return + } + file_node_platform_common_v1_cl_job_info_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_node_platform_common_v1_cl_job_info_proto_rawDesc), len(file_node_platform_common_v1_cl_job_info_proto_rawDesc)), + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_node_platform_common_v1_cl_job_info_proto_goTypes, + DependencyIndexes: file_node_platform_common_v1_cl_job_info_proto_depIdxs, + EnumInfos: file_node_platform_common_v1_cl_job_info_proto_enumTypes, + MessageInfos: file_node_platform_common_v1_cl_job_info_proto_msgTypes, + }.Build() + File_node_platform_common_v1_cl_job_info_proto = out.File + file_node_platform_common_v1_cl_job_info_proto_goTypes = nil + file_node_platform_common_v1_cl_job_info_proto_depIdxs = nil +} diff --git a/node-platform/common/v1/cl_job_info.proto b/node-platform/common/v1/cl_job_info.proto new file mode 100644 index 00000000..a47e13c9 --- /dev/null +++ b/node-platform/common/v1/cl_job_info.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package common.v1; + +option go_package = "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1"; + +// CLJobInfo is a generic, type-agnostic snapshot of a single Chainlink job. +// +// Unlike NodeJobInfo (which projects a few specific submitter/transmitter +// addresses across all jobs into a flat schema), CLJobInfo carries the job's +// common identity fields plus the complete job definition as a raw TOML string. +// This lets any job type be reported through a single schema without a +// dedicated proto message or extractor per job type. +message CLJobInfo { + // Node identity. + string csa_public_key = 1; + string node_version = 2; + string hostname = 3; + + // Job identity — fields common to every job type (they live on job.Job + // itself rather than a type-specific spec). + string external_job_id = 10; + int32 job_id = 11; + string name = 12; + string job_type = 13; + uint32 schema_version = 14; + bool forwarding_allowed = 15; + optional uint32 gas_limit = 16; + optional uint32 stream_id = 17; + string created_at = 18; + + // Complete job definition serialized as TOML. Captures all type-specific + // spec fields for any job type without requiring a per-type schema. + string spec_toml = 30; + + // Event metadata. + CLJobInfoTrigger trigger = 40; + string timestamp = 41; +} + +// CLJobInfoTrigger is the reason a CLJobInfo event was emitted. +enum CLJobInfoTrigger { + CL_JOB_INFO_TRIGGER_UNSPECIFIED = 0; + CL_JOB_INFO_TRIGGER_HEARTBEAT = 1; + CL_JOB_INFO_TRIGGER_CREATE = 2; + CL_JOB_INFO_TRIGGER_DELETE = 3; +} From 350ff458cf265fdb6fe238ee139282faadf244b2 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Fri, 4 Sep 2026 15:09:07 +0300 Subject: [PATCH 2/6] feat(node-platform): carry JD provenance on CLJobInfo, add changeset Add optional Job Distributor provenance to common.v1.CLJobInfo: feeds_manager_id (50), remote_uuid (51), spec_version (52), proposed_at (53) and approved_at (54). They are unset for jobs that did not arrive as an approved job proposal, which is how a consumer distinguishes a JD-managed job from one created directly via CLI, UI or TOML on disk. remote_uuid is the join key back to api.job.v1.Job.uuid. Document why no JD message is reused for the spec itself: neither cfm.ProposeJobRequest nor api.job.v1.Proposal models a job's contents, both carry the TOML document as an opaque string alongside identity metadata, so spec_toml follows JD's own representation rather than inventing a new one. Also add the changeset this PR was missing, covering the CLJobInfo addition as a whole. RANE-4655 Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/cl-job-info.md | 9 +++ node-platform/common/v1/cl_job_info.pb.go | 76 +++++++++++++++++++++-- node-platform/common/v1/cl_job_info.proto | 19 ++++++ 3 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 .changeset/cl-job-info.md diff --git a/.changeset/cl-job-info.md b/.changeset/cl-job-info.md new file mode 100644 index 00000000..9ca47c89 --- /dev/null +++ b/.changeset/cl-job-info.md @@ -0,0 +1,9 @@ +--- +"@chainlink/node-platform": minor +--- + +Add `common.v1.CLJobInfo`: a job-type-agnostic snapshot of a single Chainlink job, carrying +the job's common identity fields, the complete definition as a raw TOML string, and +optional Job Distributor provenance (`feeds_manager_id`, `remote_uuid`, `spec_version`, +`proposed_at`, `approved_at`). Lets any job type be reported through one schema without a +dedicated message per type. diff --git a/node-platform/common/v1/cl_job_info.pb.go b/node-platform/common/v1/cl_job_info.pb.go index a2c8abc2..7fb48f1e 100644 --- a/node-platform/common/v1/cl_job_info.pb.go +++ b/node-platform/common/v1/cl_job_info.pb.go @@ -81,6 +81,12 @@ func (CLJobInfoTrigger) EnumDescriptor() ([]byte, []int) { // common identity fields plus the complete job definition as a raw TOML string. // This lets any job type be reported through a single schema without a // dedicated proto message or extractor per job type. +// +// spec_toml deliberately mirrors how the Job Distributor already moves a job +// definition around: neither cfm.ProposeJobRequest nor api.job.v1.Proposal +// carries a structured spec, both carry the TOML document as an opaque string +// alongside identity metadata. No JD message describes a job's contents, so +// there is nothing to reuse here and this message follows the same shape. type CLJobInfo struct { state protoimpl.MessageState `protogen:"open.v1"` // Node identity. @@ -102,8 +108,20 @@ type CLJobInfo struct { // spec fields for any job type without requiring a per-type schema. SpecToml string `protobuf:"bytes,30,opt,name=spec_toml,json=specToml,proto3" json:"spec_toml,omitempty"` // Event metadata. - Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` - Timestamp string `protobuf:"bytes,41,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` + Timestamp string `protobuf:"bytes,41,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Job Distributor provenance, set only for jobs that reached the node as an + // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave + // these unset, which is itself the signal that the job is unmanaged. + FeedsManagerId *int64 `protobuf:"varint,50,opt,name=feeds_manager_id,json=feedsManagerId,proto3,oneof" json:"feeds_manager_id,omitempty"` + // remote_uuid is the proposal's UUID in the Job Distributor, i.e. the join + // key back to api.job.v1.Job.uuid. + RemoteUuid *string `protobuf:"bytes,51,opt,name=remote_uuid,json=remoteUuid,proto3,oneof" json:"remote_uuid,omitempty"` + // spec_version is the revision of the approved proposal spec that produced + // the running job. + SpecVersion *int32 `protobuf:"varint,52,opt,name=spec_version,json=specVersion,proto3,oneof" json:"spec_version,omitempty"` + ProposedAt *string `protobuf:"bytes,53,opt,name=proposed_at,json=proposedAt,proto3,oneof" json:"proposed_at,omitempty"` + ApprovedAt *string `protobuf:"bytes,54,opt,name=approved_at,json=approvedAt,proto3,oneof" json:"approved_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -243,11 +261,46 @@ func (x *CLJobInfo) GetTimestamp() string { return "" } +func (x *CLJobInfo) GetFeedsManagerId() int64 { + if x != nil && x.FeedsManagerId != nil { + return *x.FeedsManagerId + } + return 0 +} + +func (x *CLJobInfo) GetRemoteUuid() string { + if x != nil && x.RemoteUuid != nil { + return *x.RemoteUuid + } + return "" +} + +func (x *CLJobInfo) GetSpecVersion() int32 { + if x != nil && x.SpecVersion != nil { + return *x.SpecVersion + } + return 0 +} + +func (x *CLJobInfo) GetProposedAt() string { + if x != nil && x.ProposedAt != nil { + return *x.ProposedAt + } + return "" +} + +func (x *CLJobInfo) GetApprovedAt() string { + if x != nil && x.ApprovedAt != nil { + return *x.ApprovedAt + } + return "" +} + var File_node_platform_common_v1_cl_job_info_proto protoreflect.FileDescriptor const file_node_platform_common_v1_cl_job_info_proto_rawDesc = "" + "\n" + - ")node-platform/common/v1/cl_job_info.proto\x12\tcommon.v1\"\xa5\x04\n" + + ")node-platform/common/v1/cl_job_info.proto\x12\tcommon.v1\"\xc4\x06\n" + "\tCLJobInfo\x12$\n" + "\x0ecsa_public_key\x18\x01 \x01(\tR\fcsaPublicKey\x12!\n" + "\fnode_version\x18\x02 \x01(\tR\vnodeVersion\x12\x1a\n" + @@ -265,11 +318,24 @@ const file_node_platform_common_v1_cl_job_info_proto_rawDesc = "" + "created_at\x18\x12 \x01(\tR\tcreatedAt\x12\x1b\n" + "\tspec_toml\x18\x1e \x01(\tR\bspecToml\x125\n" + "\atrigger\x18( \x01(\x0e2\x1b.common.v1.CLJobInfoTriggerR\atrigger\x12\x1c\n" + - "\ttimestamp\x18) \x01(\tR\ttimestampB\f\n" + + "\ttimestamp\x18) \x01(\tR\ttimestamp\x12-\n" + + "\x10feeds_manager_id\x182 \x01(\x03H\x02R\x0efeedsManagerId\x88\x01\x01\x12$\n" + + "\vremote_uuid\x183 \x01(\tH\x03R\n" + + "remoteUuid\x88\x01\x01\x12&\n" + + "\fspec_version\x184 \x01(\x05H\x04R\vspecVersion\x88\x01\x01\x12$\n" + + "\vproposed_at\x185 \x01(\tH\x05R\n" + + "proposedAt\x88\x01\x01\x12$\n" + + "\vapproved_at\x186 \x01(\tH\x06R\n" + + "approvedAt\x88\x01\x01B\f\n" + "\n" + "_gas_limitB\f\n" + "\n" + - "_stream_id*\x9a\x01\n" + + "_stream_idB\x13\n" + + "\x11_feeds_manager_idB\x0e\n" + + "\f_remote_uuidB\x0f\n" + + "\r_spec_versionB\x0e\n" + + "\f_proposed_atB\x0e\n" + + "\f_approved_at*\x9a\x01\n" + "\x10CLJobInfoTrigger\x12#\n" + "\x1fCL_JOB_INFO_TRIGGER_UNSPECIFIED\x10\x00\x12!\n" + "\x1dCL_JOB_INFO_TRIGGER_HEARTBEAT\x10\x01\x12\x1e\n" + diff --git a/node-platform/common/v1/cl_job_info.proto b/node-platform/common/v1/cl_job_info.proto index a47e13c9..53ac999b 100644 --- a/node-platform/common/v1/cl_job_info.proto +++ b/node-platform/common/v1/cl_job_info.proto @@ -11,6 +11,12 @@ option go_package = "github.com/smartcontractkit/chainlink-protos/node-platform/ // common identity fields plus the complete job definition as a raw TOML string. // This lets any job type be reported through a single schema without a // dedicated proto message or extractor per job type. +// +// spec_toml deliberately mirrors how the Job Distributor already moves a job +// definition around: neither cfm.ProposeJobRequest nor api.job.v1.Proposal +// carries a structured spec, both carry the TOML document as an opaque string +// alongside identity metadata. No JD message describes a job's contents, so +// there is nothing to reuse here and this message follows the same shape. message CLJobInfo { // Node identity. string csa_public_key = 1; @@ -36,6 +42,19 @@ message CLJobInfo { // Event metadata. CLJobInfoTrigger trigger = 40; string timestamp = 41; + + // Job Distributor provenance, set only for jobs that reached the node as an + // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave + // these unset, which is itself the signal that the job is unmanaged. + optional int64 feeds_manager_id = 50; + // remote_uuid is the proposal's UUID in the Job Distributor, i.e. the join + // key back to api.job.v1.Job.uuid. + optional string remote_uuid = 51; + // spec_version is the revision of the approved proposal spec that produced + // the running job. + optional int32 spec_version = 52; + optional string proposed_at = 53; + optional string approved_at = 54; } // CLJobInfoTrigger is the reason a CLJobInfo event was emitted. From 6acc8ef2b786b37fc302e981c5342b21a97f05bd Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Fri, 4 Sep 2026 17:48:46 +0300 Subject: [PATCH 3/6] feat(node-platform): use google.protobuf.Timestamp on CLJobInfo --- .changeset/cl-job-info.md | 9 --- node-platform/common/v1/cl_job_info.pb.go | 99 ++++++++++++----------- node-platform/common/v1/cl_job_info.proto | 11 ++- 3 files changed, 59 insertions(+), 60 deletions(-) delete mode 100644 .changeset/cl-job-info.md diff --git a/.changeset/cl-job-info.md b/.changeset/cl-job-info.md deleted file mode 100644 index 9ca47c89..00000000 --- a/.changeset/cl-job-info.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@chainlink/node-platform": minor ---- - -Add `common.v1.CLJobInfo`: a job-type-agnostic snapshot of a single Chainlink job, carrying -the job's common identity fields, the complete definition as a raw TOML string, and -optional Job Distributor provenance (`feeds_manager_id`, `remote_uuid`, `spec_version`, -`proposed_at`, `approved_at`). Lets any job type be reported through one schema without a -dedicated message per type. diff --git a/node-platform/common/v1/cl_job_info.pb.go b/node-platform/common/v1/cl_job_info.pb.go index 7fb48f1e..a68f0601 100644 --- a/node-platform/common/v1/cl_job_info.pb.go +++ b/node-platform/common/v1/cl_job_info.pb.go @@ -9,6 +9,7 @@ package v1 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -95,21 +96,21 @@ type CLJobInfo struct { Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"` // Job identity — fields common to every job type (they live on job.Job // itself rather than a type-specific spec). - ExternalJobId string `protobuf:"bytes,10,opt,name=external_job_id,json=externalJobId,proto3" json:"external_job_id,omitempty"` - JobId int32 `protobuf:"varint,11,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` - Name string `protobuf:"bytes,12,opt,name=name,proto3" json:"name,omitempty"` - JobType string `protobuf:"bytes,13,opt,name=job_type,json=jobType,proto3" json:"job_type,omitempty"` - SchemaVersion uint32 `protobuf:"varint,14,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` - ForwardingAllowed bool `protobuf:"varint,15,opt,name=forwarding_allowed,json=forwardingAllowed,proto3" json:"forwarding_allowed,omitempty"` - GasLimit *uint32 `protobuf:"varint,16,opt,name=gas_limit,json=gasLimit,proto3,oneof" json:"gas_limit,omitempty"` - StreamId *uint32 `protobuf:"varint,17,opt,name=stream_id,json=streamId,proto3,oneof" json:"stream_id,omitempty"` - CreatedAt string `protobuf:"bytes,18,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExternalJobId string `protobuf:"bytes,10,opt,name=external_job_id,json=externalJobId,proto3" json:"external_job_id,omitempty"` + JobId int32 `protobuf:"varint,11,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Name string `protobuf:"bytes,12,opt,name=name,proto3" json:"name,omitempty"` + JobType string `protobuf:"bytes,13,opt,name=job_type,json=jobType,proto3" json:"job_type,omitempty"` + SchemaVersion uint32 `protobuf:"varint,14,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` + ForwardingAllowed bool `protobuf:"varint,15,opt,name=forwarding_allowed,json=forwardingAllowed,proto3" json:"forwarding_allowed,omitempty"` + GasLimit *uint32 `protobuf:"varint,16,opt,name=gas_limit,json=gasLimit,proto3,oneof" json:"gas_limit,omitempty"` + StreamId *uint32 `protobuf:"varint,17,opt,name=stream_id,json=streamId,proto3,oneof" json:"stream_id,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,18,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // Complete job definition serialized as TOML. Captures all type-specific // spec fields for any job type without requiring a per-type schema. SpecToml string `protobuf:"bytes,30,opt,name=spec_toml,json=specToml,proto3" json:"spec_toml,omitempty"` // Event metadata. - Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` - Timestamp string `protobuf:"bytes,41,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,41,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Job Distributor provenance, set only for jobs that reached the node as an // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave // these unset, which is itself the signal that the job is unmanaged. @@ -119,9 +120,10 @@ type CLJobInfo struct { RemoteUuid *string `protobuf:"bytes,51,opt,name=remote_uuid,json=remoteUuid,proto3,oneof" json:"remote_uuid,omitempty"` // spec_version is the revision of the approved proposal spec that produced // the running job. - SpecVersion *int32 `protobuf:"varint,52,opt,name=spec_version,json=specVersion,proto3,oneof" json:"spec_version,omitempty"` - ProposedAt *string `protobuf:"bytes,53,opt,name=proposed_at,json=proposedAt,proto3,oneof" json:"proposed_at,omitempty"` - ApprovedAt *string `protobuf:"bytes,54,opt,name=approved_at,json=approvedAt,proto3,oneof" json:"approved_at,omitempty"` + SpecVersion *int32 `protobuf:"varint,52,opt,name=spec_version,json=specVersion,proto3,oneof" json:"spec_version,omitempty"` + // Unset (nil) rather than the epoch when the job has no approved proposal. + ProposedAt *timestamppb.Timestamp `protobuf:"bytes,53,opt,name=proposed_at,json=proposedAt,proto3" json:"proposed_at,omitempty"` + ApprovedAt *timestamppb.Timestamp `protobuf:"bytes,54,opt,name=approved_at,json=approvedAt,proto3" json:"approved_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -233,11 +235,11 @@ func (x *CLJobInfo) GetStreamId() uint32 { return 0 } -func (x *CLJobInfo) GetCreatedAt() string { +func (x *CLJobInfo) GetCreatedAt() *timestamppb.Timestamp { if x != nil { return x.CreatedAt } - return "" + return nil } func (x *CLJobInfo) GetSpecToml() string { @@ -254,11 +256,11 @@ func (x *CLJobInfo) GetTrigger() CLJobInfoTrigger { return CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_UNSPECIFIED } -func (x *CLJobInfo) GetTimestamp() string { +func (x *CLJobInfo) GetTimestamp() *timestamppb.Timestamp { if x != nil { return x.Timestamp } - return "" + return nil } func (x *CLJobInfo) GetFeedsManagerId() int64 { @@ -282,25 +284,25 @@ func (x *CLJobInfo) GetSpecVersion() int32 { return 0 } -func (x *CLJobInfo) GetProposedAt() string { - if x != nil && x.ProposedAt != nil { - return *x.ProposedAt +func (x *CLJobInfo) GetProposedAt() *timestamppb.Timestamp { + if x != nil { + return x.ProposedAt } - return "" + return nil } -func (x *CLJobInfo) GetApprovedAt() string { - if x != nil && x.ApprovedAt != nil { - return *x.ApprovedAt +func (x *CLJobInfo) GetApprovedAt() *timestamppb.Timestamp { + if x != nil { + return x.ApprovedAt } - return "" + return nil } var File_node_platform_common_v1_cl_job_info_proto protoreflect.FileDescriptor const file_node_platform_common_v1_cl_job_info_proto_rawDesc = "" + "\n" + - ")node-platform/common/v1/cl_job_info.proto\x12\tcommon.v1\"\xc4\x06\n" + + ")node-platform/common/v1/cl_job_info.proto\x12\tcommon.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8a\a\n" + "\tCLJobInfo\x12$\n" + "\x0ecsa_public_key\x18\x01 \x01(\tR\fcsaPublicKey\x12!\n" + "\fnode_version\x18\x02 \x01(\tR\vnodeVersion\x12\x1a\n" + @@ -313,29 +315,27 @@ const file_node_platform_common_v1_cl_job_info_proto_rawDesc = "" + "\x0eschema_version\x18\x0e \x01(\rR\rschemaVersion\x12-\n" + "\x12forwarding_allowed\x18\x0f \x01(\bR\x11forwardingAllowed\x12 \n" + "\tgas_limit\x18\x10 \x01(\rH\x00R\bgasLimit\x88\x01\x01\x12 \n" + - "\tstream_id\x18\x11 \x01(\rH\x01R\bstreamId\x88\x01\x01\x12\x1d\n" + + "\tstream_id\x18\x11 \x01(\rH\x01R\bstreamId\x88\x01\x01\x129\n" + "\n" + - "created_at\x18\x12 \x01(\tR\tcreatedAt\x12\x1b\n" + + "created_at\x18\x12 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x1b\n" + "\tspec_toml\x18\x1e \x01(\tR\bspecToml\x125\n" + - "\atrigger\x18( \x01(\x0e2\x1b.common.v1.CLJobInfoTriggerR\atrigger\x12\x1c\n" + - "\ttimestamp\x18) \x01(\tR\ttimestamp\x12-\n" + + "\atrigger\x18( \x01(\x0e2\x1b.common.v1.CLJobInfoTriggerR\atrigger\x128\n" + + "\ttimestamp\x18) \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12-\n" + "\x10feeds_manager_id\x182 \x01(\x03H\x02R\x0efeedsManagerId\x88\x01\x01\x12$\n" + "\vremote_uuid\x183 \x01(\tH\x03R\n" + "remoteUuid\x88\x01\x01\x12&\n" + - "\fspec_version\x184 \x01(\x05H\x04R\vspecVersion\x88\x01\x01\x12$\n" + - "\vproposed_at\x185 \x01(\tH\x05R\n" + - "proposedAt\x88\x01\x01\x12$\n" + - "\vapproved_at\x186 \x01(\tH\x06R\n" + - "approvedAt\x88\x01\x01B\f\n" + + "\fspec_version\x184 \x01(\x05H\x04R\vspecVersion\x88\x01\x01\x12;\n" + + "\vproposed_at\x185 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "proposedAt\x12;\n" + + "\vapproved_at\x186 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "approvedAtB\f\n" + "\n" + "_gas_limitB\f\n" + "\n" + "_stream_idB\x13\n" + "\x11_feeds_manager_idB\x0e\n" + "\f_remote_uuidB\x0f\n" + - "\r_spec_versionB\x0e\n" + - "\f_proposed_atB\x0e\n" + - "\f_approved_at*\x9a\x01\n" + + "\r_spec_version*\x9a\x01\n" + "\x10CLJobInfoTrigger\x12#\n" + "\x1fCL_JOB_INFO_TRIGGER_UNSPECIFIED\x10\x00\x12!\n" + "\x1dCL_JOB_INFO_TRIGGER_HEARTBEAT\x10\x01\x12\x1e\n" + @@ -357,16 +357,21 @@ func file_node_platform_common_v1_cl_job_info_proto_rawDescGZIP() []byte { var file_node_platform_common_v1_cl_job_info_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_node_platform_common_v1_cl_job_info_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_node_platform_common_v1_cl_job_info_proto_goTypes = []any{ - (CLJobInfoTrigger)(0), // 0: common.v1.CLJobInfoTrigger - (*CLJobInfo)(nil), // 1: common.v1.CLJobInfo + (CLJobInfoTrigger)(0), // 0: common.v1.CLJobInfoTrigger + (*CLJobInfo)(nil), // 1: common.v1.CLJobInfo + (*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp } var file_node_platform_common_v1_cl_job_info_proto_depIdxs = []int32{ - 0, // 0: common.v1.CLJobInfo.trigger:type_name -> common.v1.CLJobInfoTrigger - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 2, // 0: common.v1.CLJobInfo.created_at:type_name -> google.protobuf.Timestamp + 0, // 1: common.v1.CLJobInfo.trigger:type_name -> common.v1.CLJobInfoTrigger + 2, // 2: common.v1.CLJobInfo.timestamp:type_name -> google.protobuf.Timestamp + 2, // 3: common.v1.CLJobInfo.proposed_at:type_name -> google.protobuf.Timestamp + 2, // 4: common.v1.CLJobInfo.approved_at:type_name -> google.protobuf.Timestamp + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_node_platform_common_v1_cl_job_info_proto_init() } diff --git a/node-platform/common/v1/cl_job_info.proto b/node-platform/common/v1/cl_job_info.proto index 53ac999b..a7579840 100644 --- a/node-platform/common/v1/cl_job_info.proto +++ b/node-platform/common/v1/cl_job_info.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package common.v1; +import "google/protobuf/timestamp.proto"; + option go_package = "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1"; // CLJobInfo is a generic, type-agnostic snapshot of a single Chainlink job. @@ -33,7 +35,7 @@ message CLJobInfo { bool forwarding_allowed = 15; optional uint32 gas_limit = 16; optional uint32 stream_id = 17; - string created_at = 18; + google.protobuf.Timestamp created_at = 18; // Complete job definition serialized as TOML. Captures all type-specific // spec fields for any job type without requiring a per-type schema. @@ -41,7 +43,7 @@ message CLJobInfo { // Event metadata. CLJobInfoTrigger trigger = 40; - string timestamp = 41; + google.protobuf.Timestamp timestamp = 41; // Job Distributor provenance, set only for jobs that reached the node as an // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave @@ -53,8 +55,9 @@ message CLJobInfo { // spec_version is the revision of the approved proposal spec that produced // the running job. optional int32 spec_version = 52; - optional string proposed_at = 53; - optional string approved_at = 54; + // Unset (nil) rather than the epoch when the job has no approved proposal. + google.protobuf.Timestamp proposed_at = 53; + google.protobuf.Timestamp approved_at = 54; } // CLJobInfoTrigger is the reason a CLJobInfo event was emitted. From 581aff049b7cff651c029339ed23702d3d01acc2 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Mon, 7 Sep 2026 21:22:40 +0300 Subject: [PATCH 4/6] replace google.protobuf.Timestamp with int64 --- node-platform/common/v1/cl_job_info.pb.go | 125 ++++++++++++---------- node-platform/common/v1/cl_job_info.proto | 25 +++-- 2 files changed, 84 insertions(+), 66 deletions(-) diff --git a/node-platform/common/v1/cl_job_info.pb.go b/node-platform/common/v1/cl_job_info.pb.go index a68f0601..73cf1e85 100644 --- a/node-platform/common/v1/cl_job_info.pb.go +++ b/node-platform/common/v1/cl_job_info.pb.go @@ -9,7 +9,6 @@ package v1 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -88,6 +87,15 @@ func (CLJobInfoTrigger) EnumDescriptor() ([]byte, []int) { // carries a structured spec, both carry the TOML document as an opaque string // alongside identity metadata. No JD message describes a job's contents, so // there is nothing to reuse here and this message follows the same shape. +// +// Timestamps are int64 epoch milliseconds rather than google.protobuf.Timestamp. +// No proto registered with chip-ingress from any domain imports a well-known +// type — every import in node-platform and data-feeds is a local, same-domain +// file. This message originally used google.protobuf.Timestamp, registered +// against staging successfully, and then never had a table created, while +// NodeBuildInfo from the same node and the same emitter kept landing. Enums and +// `optional` are fine on this path: data-feeds job_spec_event.proto uses both +// and has a table. type CLJobInfo struct { state protoimpl.MessageState `protogen:"open.v1"` // Node identity. @@ -96,21 +104,26 @@ type CLJobInfo struct { Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"` // Job identity — fields common to every job type (they live on job.Job // itself rather than a type-specific spec). - ExternalJobId string `protobuf:"bytes,10,opt,name=external_job_id,json=externalJobId,proto3" json:"external_job_id,omitempty"` - JobId int32 `protobuf:"varint,11,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` - Name string `protobuf:"bytes,12,opt,name=name,proto3" json:"name,omitempty"` - JobType string `protobuf:"bytes,13,opt,name=job_type,json=jobType,proto3" json:"job_type,omitempty"` - SchemaVersion uint32 `protobuf:"varint,14,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` - ForwardingAllowed bool `protobuf:"varint,15,opt,name=forwarding_allowed,json=forwardingAllowed,proto3" json:"forwarding_allowed,omitempty"` - GasLimit *uint32 `protobuf:"varint,16,opt,name=gas_limit,json=gasLimit,proto3,oneof" json:"gas_limit,omitempty"` - StreamId *uint32 `protobuf:"varint,17,opt,name=stream_id,json=streamId,proto3,oneof" json:"stream_id,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,18,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExternalJobId string `protobuf:"bytes,10,opt,name=external_job_id,json=externalJobId,proto3" json:"external_job_id,omitempty"` + JobId int32 `protobuf:"varint,11,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Name string `protobuf:"bytes,12,opt,name=name,proto3" json:"name,omitempty"` + JobType string `protobuf:"bytes,13,opt,name=job_type,json=jobType,proto3" json:"job_type,omitempty"` + SchemaVersion uint32 `protobuf:"varint,14,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` + ForwardingAllowed bool `protobuf:"varint,15,opt,name=forwarding_allowed,json=forwardingAllowed,proto3" json:"forwarding_allowed,omitempty"` + GasLimit *uint32 `protobuf:"varint,16,opt,name=gas_limit,json=gasLimit,proto3,oneof" json:"gas_limit,omitempty"` + StreamId *uint32 `protobuf:"varint,17,opt,name=stream_id,json=streamId,proto3,oneof" json:"stream_id,omitempty"` + // Unix epoch milliseconds, UTC; unset when the job has no creation time. + // Milliseconds rather than an RFC3339 string because Go trims trailing zeros + // from the fractional seconds, which makes those strings variable-width and + // not lexicographically ordered: a whole-second value sorts after every + // sub-second value in the same second. + CreatedAtMs *int64 `protobuf:"varint,18,opt,name=created_at_ms,json=createdAtMs,proto3,oneof" json:"created_at_ms,omitempty"` // Complete job definition serialized as TOML. Captures all type-specific // spec fields for any job type without requiring a per-type schema. SpecToml string `protobuf:"bytes,30,opt,name=spec_toml,json=specToml,proto3" json:"spec_toml,omitempty"` // Event metadata. - Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` - Timestamp *timestamppb.Timestamp `protobuf:"bytes,41,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` + TimestampMs int64 `protobuf:"varint,41,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` // Job Distributor provenance, set only for jobs that reached the node as an // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave // these unset, which is itself the signal that the job is unmanaged. @@ -120,10 +133,9 @@ type CLJobInfo struct { RemoteUuid *string `protobuf:"bytes,51,opt,name=remote_uuid,json=remoteUuid,proto3,oneof" json:"remote_uuid,omitempty"` // spec_version is the revision of the approved proposal spec that produced // the running job. - SpecVersion *int32 `protobuf:"varint,52,opt,name=spec_version,json=specVersion,proto3,oneof" json:"spec_version,omitempty"` - // Unset (nil) rather than the epoch when the job has no approved proposal. - ProposedAt *timestamppb.Timestamp `protobuf:"bytes,53,opt,name=proposed_at,json=proposedAt,proto3" json:"proposed_at,omitempty"` - ApprovedAt *timestamppb.Timestamp `protobuf:"bytes,54,opt,name=approved_at,json=approvedAt,proto3" json:"approved_at,omitempty"` + SpecVersion *int32 `protobuf:"varint,52,opt,name=spec_version,json=specVersion,proto3,oneof" json:"spec_version,omitempty"` + ProposedAtMs *int64 `protobuf:"varint,53,opt,name=proposed_at_ms,json=proposedAtMs,proto3,oneof" json:"proposed_at_ms,omitempty"` + ApprovedAtMs *int64 `protobuf:"varint,54,opt,name=approved_at_ms,json=approvedAtMs,proto3,oneof" json:"approved_at_ms,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -235,11 +247,11 @@ func (x *CLJobInfo) GetStreamId() uint32 { return 0 } -func (x *CLJobInfo) GetCreatedAt() *timestamppb.Timestamp { - if x != nil { - return x.CreatedAt +func (x *CLJobInfo) GetCreatedAtMs() int64 { + if x != nil && x.CreatedAtMs != nil { + return *x.CreatedAtMs } - return nil + return 0 } func (x *CLJobInfo) GetSpecToml() string { @@ -256,11 +268,11 @@ func (x *CLJobInfo) GetTrigger() CLJobInfoTrigger { return CLJobInfoTrigger_CL_JOB_INFO_TRIGGER_UNSPECIFIED } -func (x *CLJobInfo) GetTimestamp() *timestamppb.Timestamp { +func (x *CLJobInfo) GetTimestampMs() int64 { if x != nil { - return x.Timestamp + return x.TimestampMs } - return nil + return 0 } func (x *CLJobInfo) GetFeedsManagerId() int64 { @@ -284,25 +296,25 @@ func (x *CLJobInfo) GetSpecVersion() int32 { return 0 } -func (x *CLJobInfo) GetProposedAt() *timestamppb.Timestamp { - if x != nil { - return x.ProposedAt +func (x *CLJobInfo) GetProposedAtMs() int64 { + if x != nil && x.ProposedAtMs != nil { + return *x.ProposedAtMs } - return nil + return 0 } -func (x *CLJobInfo) GetApprovedAt() *timestamppb.Timestamp { - if x != nil { - return x.ApprovedAt +func (x *CLJobInfo) GetApprovedAtMs() int64 { + if x != nil && x.ApprovedAtMs != nil { + return *x.ApprovedAtMs } - return nil + return 0 } var File_node_platform_common_v1_cl_job_info_proto protoreflect.FileDescriptor const file_node_platform_common_v1_cl_job_info_proto_rawDesc = "" + "\n" + - ")node-platform/common/v1/cl_job_info.proto\x12\tcommon.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8a\a\n" + + ")node-platform/common/v1/cl_job_info.proto\x12\tcommon.v1\"\xf5\x06\n" + "\tCLJobInfo\x12$\n" + "\x0ecsa_public_key\x18\x01 \x01(\tR\fcsaPublicKey\x12!\n" + "\fnode_version\x18\x02 \x01(\tR\vnodeVersion\x12\x1a\n" + @@ -315,27 +327,27 @@ const file_node_platform_common_v1_cl_job_info_proto_rawDesc = "" + "\x0eschema_version\x18\x0e \x01(\rR\rschemaVersion\x12-\n" + "\x12forwarding_allowed\x18\x0f \x01(\bR\x11forwardingAllowed\x12 \n" + "\tgas_limit\x18\x10 \x01(\rH\x00R\bgasLimit\x88\x01\x01\x12 \n" + - "\tstream_id\x18\x11 \x01(\rH\x01R\bstreamId\x88\x01\x01\x129\n" + - "\n" + - "created_at\x18\x12 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x1b\n" + + "\tstream_id\x18\x11 \x01(\rH\x01R\bstreamId\x88\x01\x01\x12'\n" + + "\rcreated_at_ms\x18\x12 \x01(\x03H\x02R\vcreatedAtMs\x88\x01\x01\x12\x1b\n" + "\tspec_toml\x18\x1e \x01(\tR\bspecToml\x125\n" + - "\atrigger\x18( \x01(\x0e2\x1b.common.v1.CLJobInfoTriggerR\atrigger\x128\n" + - "\ttimestamp\x18) \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12-\n" + - "\x10feeds_manager_id\x182 \x01(\x03H\x02R\x0efeedsManagerId\x88\x01\x01\x12$\n" + - "\vremote_uuid\x183 \x01(\tH\x03R\n" + + "\atrigger\x18( \x01(\x0e2\x1b.common.v1.CLJobInfoTriggerR\atrigger\x12!\n" + + "\ftimestamp_ms\x18) \x01(\x03R\vtimestampMs\x12-\n" + + "\x10feeds_manager_id\x182 \x01(\x03H\x03R\x0efeedsManagerId\x88\x01\x01\x12$\n" + + "\vremote_uuid\x183 \x01(\tH\x04R\n" + "remoteUuid\x88\x01\x01\x12&\n" + - "\fspec_version\x184 \x01(\x05H\x04R\vspecVersion\x88\x01\x01\x12;\n" + - "\vproposed_at\x185 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "proposedAt\x12;\n" + - "\vapproved_at\x186 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "approvedAtB\f\n" + + "\fspec_version\x184 \x01(\x05H\x05R\vspecVersion\x88\x01\x01\x12)\n" + + "\x0eproposed_at_ms\x185 \x01(\x03H\x06R\fproposedAtMs\x88\x01\x01\x12)\n" + + "\x0eapproved_at_ms\x186 \x01(\x03H\aR\fapprovedAtMs\x88\x01\x01B\f\n" + "\n" + "_gas_limitB\f\n" + "\n" + - "_stream_idB\x13\n" + + "_stream_idB\x10\n" + + "\x0e_created_at_msB\x13\n" + "\x11_feeds_manager_idB\x0e\n" + "\f_remote_uuidB\x0f\n" + - "\r_spec_version*\x9a\x01\n" + + "\r_spec_versionB\x11\n" + + "\x0f_proposed_at_msB\x11\n" + + "\x0f_approved_at_ms*\x9a\x01\n" + "\x10CLJobInfoTrigger\x12#\n" + "\x1fCL_JOB_INFO_TRIGGER_UNSPECIFIED\x10\x00\x12!\n" + "\x1dCL_JOB_INFO_TRIGGER_HEARTBEAT\x10\x01\x12\x1e\n" + @@ -357,21 +369,16 @@ func file_node_platform_common_v1_cl_job_info_proto_rawDescGZIP() []byte { var file_node_platform_common_v1_cl_job_info_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_node_platform_common_v1_cl_job_info_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_node_platform_common_v1_cl_job_info_proto_goTypes = []any{ - (CLJobInfoTrigger)(0), // 0: common.v1.CLJobInfoTrigger - (*CLJobInfo)(nil), // 1: common.v1.CLJobInfo - (*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp + (CLJobInfoTrigger)(0), // 0: common.v1.CLJobInfoTrigger + (*CLJobInfo)(nil), // 1: common.v1.CLJobInfo } var file_node_platform_common_v1_cl_job_info_proto_depIdxs = []int32{ - 2, // 0: common.v1.CLJobInfo.created_at:type_name -> google.protobuf.Timestamp - 0, // 1: common.v1.CLJobInfo.trigger:type_name -> common.v1.CLJobInfoTrigger - 2, // 2: common.v1.CLJobInfo.timestamp:type_name -> google.protobuf.Timestamp - 2, // 3: common.v1.CLJobInfo.proposed_at:type_name -> google.protobuf.Timestamp - 2, // 4: common.v1.CLJobInfo.approved_at:type_name -> google.protobuf.Timestamp - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 0, // 0: common.v1.CLJobInfo.trigger:type_name -> common.v1.CLJobInfoTrigger + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_node_platform_common_v1_cl_job_info_proto_init() } diff --git a/node-platform/common/v1/cl_job_info.proto b/node-platform/common/v1/cl_job_info.proto index a7579840..9df54315 100644 --- a/node-platform/common/v1/cl_job_info.proto +++ b/node-platform/common/v1/cl_job_info.proto @@ -2,8 +2,6 @@ syntax = "proto3"; package common.v1; -import "google/protobuf/timestamp.proto"; - option go_package = "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1"; // CLJobInfo is a generic, type-agnostic snapshot of a single Chainlink job. @@ -19,6 +17,15 @@ option go_package = "github.com/smartcontractkit/chainlink-protos/node-platform/ // carries a structured spec, both carry the TOML document as an opaque string // alongside identity metadata. No JD message describes a job's contents, so // there is nothing to reuse here and this message follows the same shape. +// +// Timestamps are int64 epoch milliseconds rather than google.protobuf.Timestamp. +// No proto registered with chip-ingress from any domain imports a well-known +// type — every import in node-platform and data-feeds is a local, same-domain +// file. This message originally used google.protobuf.Timestamp, registered +// against staging successfully, and then never had a table created, while +// NodeBuildInfo from the same node and the same emitter kept landing. Enums and +// `optional` are fine on this path: data-feeds job_spec_event.proto uses both +// and has a table. message CLJobInfo { // Node identity. string csa_public_key = 1; @@ -35,7 +42,12 @@ message CLJobInfo { bool forwarding_allowed = 15; optional uint32 gas_limit = 16; optional uint32 stream_id = 17; - google.protobuf.Timestamp created_at = 18; + // Unix epoch milliseconds, UTC; unset when the job has no creation time. + // Milliseconds rather than an RFC3339 string because Go trims trailing zeros + // from the fractional seconds, which makes those strings variable-width and + // not lexicographically ordered: a whole-second value sorts after every + // sub-second value in the same second. + optional int64 created_at_ms = 18; // Complete job definition serialized as TOML. Captures all type-specific // spec fields for any job type without requiring a per-type schema. @@ -43,7 +55,7 @@ message CLJobInfo { // Event metadata. CLJobInfoTrigger trigger = 40; - google.protobuf.Timestamp timestamp = 41; + int64 timestamp_ms = 41; // Job Distributor provenance, set only for jobs that reached the node as an // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave @@ -55,9 +67,8 @@ message CLJobInfo { // spec_version is the revision of the approved proposal spec that produced // the running job. optional int32 spec_version = 52; - // Unset (nil) rather than the epoch when the job has no approved proposal. - google.protobuf.Timestamp proposed_at = 53; - google.protobuf.Timestamp approved_at = 54; + optional int64 proposed_at_ms = 53; + optional int64 approved_at_ms = 54; } // CLJobInfoTrigger is the reason a CLJobInfo event was emitted. From 8bc931b514cf11993de7ae13405e9c0344d44955 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Tue, 8 Sep 2026 11:29:58 +0300 Subject: [PATCH 5/6] update comments --- node-platform/common/v1/cl_job_info.proto | 53 +++++++---------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/node-platform/common/v1/cl_job_info.proto b/node-platform/common/v1/cl_job_info.proto index 9df54315..567c6926 100644 --- a/node-platform/common/v1/cl_job_info.proto +++ b/node-platform/common/v1/cl_job_info.proto @@ -4,36 +4,22 @@ package common.v1; option go_package = "github.com/smartcontractkit/chainlink-protos/node-platform/common/v1"; -// CLJobInfo is a generic, type-agnostic snapshot of a single Chainlink job. +// CLJobInfo is a type-agnostic snapshot of a single Chainlink job: common +// identity fields plus the whole job definition as TOML, so any job type is +// reported through one schema with no per-type message or extractor. This is +// also how JD moves specs around — ProposeJobRequest and Proposal both carry +// the TOML as an opaque string. // -// Unlike NodeJobInfo (which projects a few specific submitter/transmitter -// addresses across all jobs into a flat schema), CLJobInfo carries the job's -// common identity fields plus the complete job definition as a raw TOML string. -// This lets any job type be reported through a single schema without a -// dedicated proto message or extractor per job type. -// -// spec_toml deliberately mirrors how the Job Distributor already moves a job -// definition around: neither cfm.ProposeJobRequest nor api.job.v1.Proposal -// carries a structured spec, both carry the TOML document as an opaque string -// alongside identity metadata. No JD message describes a job's contents, so -// there is nothing to reuse here and this message follows the same shape. -// -// Timestamps are int64 epoch milliseconds rather than google.protobuf.Timestamp. -// No proto registered with chip-ingress from any domain imports a well-known -// type — every import in node-platform and data-feeds is a local, same-domain -// file. This message originally used google.protobuf.Timestamp, registered -// against staging successfully, and then never had a table created, while -// NodeBuildInfo from the same node and the same emitter kept landing. Enums and -// `optional` are fine on this path: data-feeds job_spec_event.proto uses both -// and has a table. +// Timestamps are int64 epoch millis, not google.protobuf.Timestamp: nothing +// registered with chip-ingress imports a WKT, and the Timestamp version +// registered fine but never got a table. message CLJobInfo { // Node identity. string csa_public_key = 1; string node_version = 2; string hostname = 3; - // Job identity — fields common to every job type (they live on job.Job - // itself rather than a type-specific spec). + // Job identity — common to every job type. string external_job_id = 10; int32 job_id = 11; string name = 12; @@ -42,30 +28,23 @@ message CLJobInfo { bool forwarding_allowed = 15; optional uint32 gas_limit = 16; optional uint32 stream_id = 17; - // Unix epoch milliseconds, UTC; unset when the job has no creation time. - // Milliseconds rather than an RFC3339 string because Go trims trailing zeros - // from the fractional seconds, which makes those strings variable-width and - // not lexicographically ordered: a whole-second value sorts after every - // sub-second value in the same second. + // Epoch millis, UTC. Not RFC3339: Go emits those at variable width, so they + // don't sort chronologically. optional int64 created_at_ms = 18; - // Complete job definition serialized as TOML. Captures all type-specific - // spec fields for any job type without requiring a per-type schema. + // Whole job definition as TOML, covering all type-specific spec fields. string spec_toml = 30; // Event metadata. CLJobInfoTrigger trigger = 40; int64 timestamp_ms = 41; - // Job Distributor provenance, set only for jobs that reached the node as an - // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave - // these unset, which is itself the signal that the job is unmanaged. + // JD provenance. Unset for jobs created directly (CLI, UI, TOML on disk), + // which is how a consumer spots an unmanaged job. optional int64 feeds_manager_id = 50; - // remote_uuid is the proposal's UUID in the Job Distributor, i.e. the join - // key back to api.job.v1.Job.uuid. + // Join key back to api.job.v1.Job.uuid. optional string remote_uuid = 51; - // spec_version is the revision of the approved proposal spec that produced - // the running job. + // Revision of the approved proposal spec that produced this job. optional int32 spec_version = 52; optional int64 proposed_at_ms = 53; optional int64 approved_at_ms = 54; From 56ba47a1f23b10a73cfe8508a2b8a6a94f1cd42a Mon Sep 17 00:00:00 2001 From: "app-token-issuer-engops[bot]" <144731339+app-token-issuer-engops[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 08:32:48 +0000 Subject: [PATCH 6/6] bot: regenerate protobuf files --- node-platform/common/v1/cl_job_info.pb.go | 53 +++++++---------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/node-platform/common/v1/cl_job_info.pb.go b/node-platform/common/v1/cl_job_info.pb.go index 73cf1e85..7d2671d3 100644 --- a/node-platform/common/v1/cl_job_info.pb.go +++ b/node-platform/common/v1/cl_job_info.pb.go @@ -74,36 +74,22 @@ func (CLJobInfoTrigger) EnumDescriptor() ([]byte, []int) { return file_node_platform_common_v1_cl_job_info_proto_rawDescGZIP(), []int{0} } -// CLJobInfo is a generic, type-agnostic snapshot of a single Chainlink job. +// CLJobInfo is a type-agnostic snapshot of a single Chainlink job: common +// identity fields plus the whole job definition as TOML, so any job type is +// reported through one schema with no per-type message or extractor. This is +// also how JD moves specs around — ProposeJobRequest and Proposal both carry +// the TOML as an opaque string. // -// Unlike NodeJobInfo (which projects a few specific submitter/transmitter -// addresses across all jobs into a flat schema), CLJobInfo carries the job's -// common identity fields plus the complete job definition as a raw TOML string. -// This lets any job type be reported through a single schema without a -// dedicated proto message or extractor per job type. -// -// spec_toml deliberately mirrors how the Job Distributor already moves a job -// definition around: neither cfm.ProposeJobRequest nor api.job.v1.Proposal -// carries a structured spec, both carry the TOML document as an opaque string -// alongside identity metadata. No JD message describes a job's contents, so -// there is nothing to reuse here and this message follows the same shape. -// -// Timestamps are int64 epoch milliseconds rather than google.protobuf.Timestamp. -// No proto registered with chip-ingress from any domain imports a well-known -// type — every import in node-platform and data-feeds is a local, same-domain -// file. This message originally used google.protobuf.Timestamp, registered -// against staging successfully, and then never had a table created, while -// NodeBuildInfo from the same node and the same emitter kept landing. Enums and -// `optional` are fine on this path: data-feeds job_spec_event.proto uses both -// and has a table. +// Timestamps are int64 epoch millis, not google.protobuf.Timestamp: nothing +// registered with chip-ingress imports a WKT, and the Timestamp version +// registered fine but never got a table. type CLJobInfo struct { state protoimpl.MessageState `protogen:"open.v1"` // Node identity. CsaPublicKey string `protobuf:"bytes,1,opt,name=csa_public_key,json=csaPublicKey,proto3" json:"csa_public_key,omitempty"` NodeVersion string `protobuf:"bytes,2,opt,name=node_version,json=nodeVersion,proto3" json:"node_version,omitempty"` Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"` - // Job identity — fields common to every job type (they live on job.Job - // itself rather than a type-specific spec). + // Job identity — common to every job type. ExternalJobId string `protobuf:"bytes,10,opt,name=external_job_id,json=externalJobId,proto3" json:"external_job_id,omitempty"` JobId int32 `protobuf:"varint,11,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` Name string `protobuf:"bytes,12,opt,name=name,proto3" json:"name,omitempty"` @@ -112,27 +98,20 @@ type CLJobInfo struct { ForwardingAllowed bool `protobuf:"varint,15,opt,name=forwarding_allowed,json=forwardingAllowed,proto3" json:"forwarding_allowed,omitempty"` GasLimit *uint32 `protobuf:"varint,16,opt,name=gas_limit,json=gasLimit,proto3,oneof" json:"gas_limit,omitempty"` StreamId *uint32 `protobuf:"varint,17,opt,name=stream_id,json=streamId,proto3,oneof" json:"stream_id,omitempty"` - // Unix epoch milliseconds, UTC; unset when the job has no creation time. - // Milliseconds rather than an RFC3339 string because Go trims trailing zeros - // from the fractional seconds, which makes those strings variable-width and - // not lexicographically ordered: a whole-second value sorts after every - // sub-second value in the same second. + // Epoch millis, UTC. Not RFC3339: Go emits those at variable width, so they + // don't sort chronologically. CreatedAtMs *int64 `protobuf:"varint,18,opt,name=created_at_ms,json=createdAtMs,proto3,oneof" json:"created_at_ms,omitempty"` - // Complete job definition serialized as TOML. Captures all type-specific - // spec fields for any job type without requiring a per-type schema. + // Whole job definition as TOML, covering all type-specific spec fields. SpecToml string `protobuf:"bytes,30,opt,name=spec_toml,json=specToml,proto3" json:"spec_toml,omitempty"` // Event metadata. Trigger CLJobInfoTrigger `protobuf:"varint,40,opt,name=trigger,proto3,enum=common.v1.CLJobInfoTrigger" json:"trigger,omitempty"` TimestampMs int64 `protobuf:"varint,41,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` - // Job Distributor provenance, set only for jobs that reached the node as an - // approved job proposal. Jobs created directly (CLI, UI, TOML on disk) leave - // these unset, which is itself the signal that the job is unmanaged. + // JD provenance. Unset for jobs created directly (CLI, UI, TOML on disk), + // which is how a consumer spots an unmanaged job. FeedsManagerId *int64 `protobuf:"varint,50,opt,name=feeds_manager_id,json=feedsManagerId,proto3,oneof" json:"feeds_manager_id,omitempty"` - // remote_uuid is the proposal's UUID in the Job Distributor, i.e. the join - // key back to api.job.v1.Job.uuid. + // Join key back to api.job.v1.Job.uuid. RemoteUuid *string `protobuf:"bytes,51,opt,name=remote_uuid,json=remoteUuid,proto3,oneof" json:"remote_uuid,omitempty"` - // spec_version is the revision of the approved proposal spec that produced - // the running job. + // Revision of the approved proposal spec that produced this job. SpecVersion *int32 `protobuf:"varint,52,opt,name=spec_version,json=specVersion,proto3,oneof" json:"spec_version,omitempty"` ProposedAtMs *int64 `protobuf:"varint,53,opt,name=proposed_at_ms,json=proposedAtMs,proto3,oneof" json:"proposed_at_ms,omitempty"` ApprovedAtMs *int64 `protobuf:"varint,54,opt,name=approved_at_ms,json=approvedAtMs,proto3,oneof" json:"approved_at_ms,omitempty"`