Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions docs/proofs/271-436-cfl-diagnostics-advisories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Proof: #436 `cfl` Diagnostics and Advisory Presenter Migration

## Scope

This proof covers presenter-owned diagnostic/advisory/status output for:

- `config test`
- `config clear`

The migrated commands now:

- orchestrate config, API, keyring, and prompt control flow only
- emit presenter-owned diagnostic/status models through
`tools/cfl/internal/present/config.go`
- preserve `config test` progress timing through an explicit no-newline

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low (harness-engineering:harness-knowledge-reviewer): The rule that all diagnostic/status output must be presenter-owned — with one-shot confirmation prompt text as the sole direct-stderr exception — is a project-wide architectural convention, but it is captured only in this ticket-tied proof file. If no durable architecture doc covers the presenter pattern (e.g., a note in the shared presenter package or a dedicated architecture doc), this convention risks being invisible to future contributors writing new commands.

Reply to this thread when addressed.

presenter message section
- keep one-shot confirmation prompt text as an explicit direct `stderr`
exception
- keep `cfl init` wizard output outside this ticket

Pagination advisories and the `page edit --legacy` warning were already moved
to presenters in earlier child tickets. `#435` contains the exact command and
live proof for the legacy-editor advisory.

## Verification Commands

Executed:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low (documentation:docs-reviewer): Ticket #435 is referenced ('contains the exact command and live proof for the legacy-editor advisory') but no link is provided. Readers cannot navigate to the associated proof without knowing where to find it.

Reply to this thread when addressed.


```bash
rtk go test ./tools/cfl/internal/cmd/configcmd ./tools/cfl/internal/present
```

Result:

```text
Go test: 57 passed in 2 packages
```

Executed:

```bash
rtk go test ./tools/cfl/internal/present ./tools/cfl/internal/cmd/configcmd ./tools/cfl/internal/cmd/page ./tools/cfl/internal/cmd/root ./shared/present
```

Result:

```text
Go test: 287 passed in 5 packages
```

Executed:

```bash
rtk golangci-lint run ./tools/cfl/internal/present ./tools/cfl/internal/cmd/configcmd ./tools/cfl/internal/cmd/page ./tools/cfl/internal/cmd/root
```

Result:

```text
golangci-lint: No issues found
```

Executed:

```bash
rtk go test ./tools/cfl/... ./shared/...
```

Result:

```text
Go test: 1637 passed in 34 packages
```

Executed:

```bash
rtk proxy git diff --check
```

Result:

```text
<no output>
```

## Grep Gates

Executed:

```bash
rtk rg -n 'fmt\.Fprint|fmt\.Fprintf|fmt\.Fprintln' tools/cfl/internal/cmd/configcmd/test.go tools/cfl/internal/cmd/configcmd/clear.go
```

Result:

```text
tools/cfl/internal/cmd/configcmd/clear.go:87: _, _ = fmt.Fprint(opts.Stderr, promptText+" [y/N]: ")
```

This is the documented prompt exception.

Executed:

```bash
rtk rg -n 'Testing connection|Troubleshooting|Authenticated as|No stored API token|Cancelled\. Nothing was cleared|Removed key|Removed the shared keyring bundle|Warning: this is the SHARED token|Note: .*environment' tools/cfl/internal/cmd/configcmd/test.go tools/cfl/internal/cmd/configcmd/clear.go
```

Result:

```text
tools/cfl/internal/cmd/configcmd/clear.go:43:Note: CFL_API_TOKEN / ATLASSIAN_API_TOKEN environment variables still
```

This remaining match is command help text, not runtime diagnostic/status
output.

## Test Evidence

Presenter tests in `tools/cfl/internal/present/config_test.go` assert exact
`OutputModel` messages and stderr routing for:

- config-test success with user details
- config-test no-newline progress
- config-test fallback success when user lookup fails after connectivity works
- config-test failure/troubleshooting
- clear planned default action
- clear planned `--all` action, including plaintext cleanup and keyring
unavailable notes
- clear cancelled
- clear success
- clear `--all` success
- no stored token
- environment override notes

Command tests now assert exact stdout/stderr for:

- `config test` success
- `config test` fallback success
- `config test` connection failure
- `config clear` no-op
- `config clear` no-op with an environment override note, without exposing the
token value
- `config clear --force` default deletion
- `config clear --force` default deletion with an environment override note,
without exposing the token value
- interactive `config clear` confirm/cancel prompt behavior
- `config clear --non-interactive` without `--force`, proving no preview or
warning leaks before `prompt.ErrConfirmationRequired`
- `config clear --non-interactive --force`
- `config clear --all`
- `config clear --all` when keyring planning fails but plaintext cleanup can
still proceed
- an executable source gate that permits only the prompt `fmt.Fprint` exception
and rejects migrated diagnostic/status strings in `configcmd/test.go` and
`configcmd/clear.go`

## Live CLI Transcript

The binary was rebuilt:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low (documentation:docs-reviewer): The live proof references a temporary directory '/tmp/cfl-436-proof.u3byyu' that does not persist. While ephemeral directories are expected for live transcripts, a brief note clarifying that the path is intentionally ephemeral would help future readers understand why the artifact is unverifiable after the fact.

Reply to this thread when addressed.


```bash
rtk go build -o ./bin/cfl ./tools/cfl/cmd/cfl
```

Transcript directory:

```text
/tmp/cfl-436-proof.u3byyu
```

Executed:

```bash
./bin/cfl --no-color config test
```

Result:

```text
exit status: 0
stdout bytes: 0
stderr bytes: 169
```

Observed stderr, with identity fields redacted:

```text
Testing connection... success!

Authentication successful
API access verified

Authenticated as: <redacted name> (<redacted email>)
Account ID: <redacted account id>
```

Executed:

```bash
./bin/cfl --no-color --non-interactive config clear
```

Result:

```text
exit status: 1
stdout bytes: 0
stderr bytes: 80
```

Observed stderr:

```text
Error: --non-interactive: confirmation required; re-run with --force to proceed
```

This proves the safe non-destructive case fails loudly before prompt preview,
shared-token warning, keyring deletion, or any token value exposure.
7 changes: 4 additions & 3 deletions shared/present/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ const (

// MessageSection displays a status message (mutations, confirmations).
type MessageSection struct {
Kind MessageKind
Message string
Stream Stream // Explicit stream routing (zero value = StreamStdout)
Kind MessageKind
Message string
Stream Stream // Explicit stream routing (zero value = StreamStdout)
NoNewline bool // For progress messages that intentionally complete later.
}

func (*MessageSection) sectionMarker() {}
Expand Down
16 changes: 10 additions & 6 deletions shared/present/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,20 +147,24 @@ func renderTSVTable(sec *TableSection) string {
}

func renderMessage(sec *MessageSection, style Style) string {
terminator := "\n"
if sec.NoNewline {
terminator = ""
}
if style == StyleAgent {
// Plain text, no decorators
return sec.Message + "\n"
return sec.Message + terminator
}
// Human style with decorators
switch sec.Kind {
case MessageSuccess:
return "✓ " + sec.Message + "\n"
return "✓ " + sec.Message + terminator
case MessageWarning:
return "⚠ " + sec.Message + "\n"
return "⚠ " + sec.Message + terminator
case MessageError:
return "✗ " + sec.Message + "\n"
return "✗ " + sec.Message + terminator
case MessageInfo:
return sec.Message + "\n"
return sec.Message + terminator
}
return sec.Message + "\n"
return sec.Message + terminator
}
18 changes: 18 additions & 0 deletions shared/present/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,24 @@ func TestRender_MessageSection_Info_Human(t *testing.T) {
}
}

func TestRender_MessageSection_NoNewline(t *testing.T) {
t.Parallel()
model := &OutputModel{
Sections: []Section{
&MessageSection{Kind: MessageInfo, Message: "Testing connection... ", Stream: StreamStderr, NoNewline: true},
&MessageSection{Kind: MessageInfo, Message: "success!", Stream: StreamStderr},
},
}

out := Render(model, StyleHuman)
if out.Stdout != "" {
t.Errorf("progress stdout should be empty, got: %q", out.Stdout)
}
if out.Stderr != "Testing connection... success!\n" {
t.Errorf("progress stderr:\ngot: %q\nwant: %q", out.Stderr, "Testing connection... success!\n")
}
}

func TestRender_MixedSections_WithWarning(t *testing.T) {
t.Parallel()
model := &OutputModel{
Expand Down
Loading
Loading