Skip to content

Add doctl support for Signals consent and export APIs - #2019

Open
sauravk-digitalocean wants to merge 5 commits into
digitalocean:mainfrom
sauravk-digitalocean:sauravkumar/fi-610-doctl-support-signals-api
Open

sauravk-digitalocean wants to merge 5 commits into
digitalocean:mainfrom
sauravk-digitalocean:sauravkumar/fi-610-doctl-support-signals-api

Conversation

@sauravk-digitalocean

Copy link
Copy Markdown

Add doctl signals command group with consent and export subcommands for interacting with the Signals collection consent and bulk export APIs.

Consent commands (consent-gateway /v1/consent):
doctl signals consent list - List all consents for the team
doctl signals consent get - Get consent for one agent
doctl signals consent set - Enable or disable consent for an agent

Export commands (signals-api /v1/signals/exports):
doctl signals export list - List export jobs
doctl signals export create - Start a bulk export (idempotent)
doctl signals export get - Poll/get an export job by ID

New files:
do/signals.go - SignalsService interface + godo-based impl
do/mocks/SignalsService.go - Mock for unit tests
commands/signals_consent.go - Consent CLI commands
commands/signals_export.go - Export CLI commands
commands/displayers/signals_consent.go - Consent table/JSON formatter
commands/displayers/signals_export.go - Export table/JSON formatter
commands/signals_consent_test.go - 5 unit tests for consent commands
commands/signals_export_test.go - 4 unit tests for export commands

Modified:
args.go - Signals argument constants
commands/command_config.go - Wire SignalsService into CmdConfig
commands/commands_test.go - Add signals mock to test infrastructure
commands/doit.go - Register Signals() command group

FI-610

sauravk-digitalocean and others added 2 commits October 6, 2026 18:15
Add `doctl signals` command group with consent and export subcommands
for interacting with the Signals collection consent and bulk export APIs.

Consent commands (consent-gateway /v1/consent):
  doctl signals consent list     - List all consents for the team
  doctl signals consent get      - Get consent for one agent
  doctl signals consent set      - Enable or disable consent for an agent

Export commands (signals-api /v1/signals/exports):
  doctl signals export list      - List export jobs
  doctl signals export create    - Start a bulk export (idempotent)
  doctl signals export get       - Poll/get an export job by ID

New files:
  do/signals.go                         - SignalsService interface + godo-based impl
  do/mocks/SignalsService.go            - Mock for unit tests
  commands/signals_consent.go           - Consent CLI commands
  commands/signals_export.go            - Export CLI commands
  commands/displayers/signals_consent.go - Consent table/JSON formatter
  commands/displayers/signals_export.go  - Export table/JSON formatter
  commands/signals_consent_test.go      - 5 unit tests for consent commands
  commands/signals_export_test.go       - 4 unit tests for export commands

Modified:
  args.go                    - Signals argument constants
  commands/command_config.go - Wire SignalsService into CmdConfig
  commands/commands_test.go  - Add signals mock to test infrastructure
  commands/doit.go           - Register Signals() command group

FI-610

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…-gateway

Create export uses signal_type (not signal_types); list unwraps {edges,page_info};
job ids/timestamps match ExportJob (export_id, unix seconds); download and
options are separate GETs. Weekly trigger stays on signal_types.

Co-authored-by: Cursor <cursoragent@cursor.com>
@aupadhyay-shark

Copy link
Copy Markdown
Contributor

Could not push to this fork (no write access). Contract alignment vs live signals-api / consent-gateway is in:

Create export JSON field is signal_type (array), not signal_types. The API uses DisallowUnknownFields, so the old key 400s. signal_types is only PUT /v1/signals/export-trigger.

Also fixed: list {edges,page_info}, export_id + unix timestamps, separate GET .../download and GET .../options, get cobra Use trailing space, session_ids / concerning / category / layer.

Please merge the fork PRs (or cherry-pick 8c42378 / 04bf6a8) so this PR matches production.

@aupadhyay-shark

Copy link
Copy Markdown
Contributor

godo companion: sauravk-digitalocean/godo#1 (onto digitalocean/godo#1140).

sauravk-digitalocean and others added 2 commits October 6, 2026 20:28
Align Signals client with live signals-api
Co-authored-by: Cursor <cursoragent@cursor.com>

@nhenning-do nhenning-do left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

main change request, we need to import from godo rather than re-declaring types. Happy to help update but would need write perms.

Comment thread do/signals.go

// SignalsService talks to consent-gateway and signals-api over the public
// Oceanus paths (same as Cloud UI).
type SignalsService interface {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This layer hand-rolls HTTP rather than delegating to godo. There are 10 client.NewRequest calls in this file (lines 156, 173, 198, 238, 258, 275, 292, 305, 318, 334) and no calls into client.Signals. Every other doctl service delegates to a godo service, so APICLI will likely ask for this before merge.

do/gradientai.go is the pattern:

// :23  wrapper type, no fields redeclared
type Agent struct { *godo.Agent }
type Agents []Agent

// :87  request types come straight from godo, no doctl copies
CreateKnowledgeBase(req *godo.KnowledgeBaseCreateRequest) (*KnowledgeBase, error)

// :190  one call, discard *godo.Response
func (a *agentPlatformService) GetKnowledgeBase(id string) (*KnowledgeBase, error) {
    kb, _, _, err := a.client.AgentPlatform.GetKnowledgeBase(context.TODO(), id)
    if err != nil {
        return nil, err
    }
    return &KnowledgeBase{KnowledgeBase: kb}, nil
}

context.TODO() is correct style in this layer, and the constructor at :146 is correct too. It is the local types and the method bodies that would change.

This is blocked on a godo tag. go.mod pins godo v1.217.0, which predates the Signals client merged in #1139. A local replace directive works for development but fails CI, so the tag needs to come first.

Comment thread do/signals.go

// SignalsConsent is one (team, agent) collection-consent row from
// consent-gateway GET/PUT /v1/consent.
type SignalsConsent struct {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These local types duplicate what godo now owns. Once this delegates to godo, the whole block becomes thin wrappers:

type SignalsConsent struct { *godo.SignalsConsentRecord }
type SignalsConsents []SignalsConsent
type SignalsExport struct { *godo.SignalsExportJob }
type SignalsExports []SignalsExport

and the request/options types can be used from godo directly instead of being redeclared here.

displayerType(&displayers.SignalsConsent{}),
)
AddStringFlag(cmdConsentSet, doctl.ArgSignalsAgentID, "", "", "The agent ID to set consent for.", requiredOpt())
AddBoolFlag(cmdConsentSet, doctl.ArgSignalsEnabled, "", true, "Enable (true) or disable (false) Signals collection.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This defaults --enabled to true, so doctl signals consent set --agent-id X with no flag turns data collection on. I don't think we should default to true for a consent control mechanism. Suggest requiredOpt() so the intent has to be stated, or default false.

func TestSignalsExportList(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
tm.signals.EXPECT().ListExports(&do.SignalsExportListOptions{
Limit: 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This asserts Limit: 0 but the flag default is 20 (commands/signals_export.go line 54). It passes only because withTestClient does not apply cobra defaults, so the test asserts behavior that contradicts the declared default. We should either assert 20 or set the flag explicitly in the test.

@anup-deka

Copy link
Copy Markdown
Contributor

You would need to update the godo version to v1.218.0

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.

5 participants