Skip to content

fix(init): show reviewer credential status#351

Merged
rianjs merged 5 commits into
mainfrom
feat/348-reviewer-credential-status
Jun 18, 2026
Merged

fix(init): show reviewer credential status#351
rianjs merged 5 commits into
mainfrom
feat/348-reviewer-credential-status

Conversation

@rianjs

@rianjs rianjs commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • show reviewer credential readiness in the reviewer entity editor, including destination and per-key state
  • preserve draft-local staged/deferred/skipped state when re-entering reviewer setup
  • document the reviewer credential status contract

Verification

  • tmux @ 120x40, fresh GitHub App reviewer: saw required github_app_id and github_app_private_key as missing, optional github_app_installation_id as optional
  • tmux @ 120x40, after entering hidden draft values and skipping installation id: re-entry showed staged, staged, and skipped optional; raw sentinel values were not rendered
  • tmux @ 120x40, seeded PAT reviewer in file backend: saw git_token: existing; raw token was not rendered
  • make tidy
  • make lint
  • make test
  • make build

Closes #348

@rianjs

rianjs commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Findings

Major: Reviewer credential status can become stale when the user edits Reviewer secret location. In initReviewerEntityLinearEditor, field sync only runs for selection/action changes, and the status is recomputed from seed/selection rather than the current document value. That means changing from an existing ref to a new custom ref can still show the old destination and existing/staged state. See init_reviewer_entity_editor.go:276 and init_reviewer_credential_status.go:160. The status needs to derive from the current credential-ref input and update when that field changes. Add a regression where an existing PAT ref is changed to a new custom ref and the status no longer shows the old ref as existing.

Major: Status only inspects the currently planned reviewer credential entry, not every selectable reviewer entity. buildInteractiveInitReviewerCredentialStatuses replans only the active workspace profile, then initReviewerCredentialStatusForSelection falls back to synthesizeReviewerCredentialStatus for other selected entities, which never checks the backend. A reusable reviewer entity from another profile with an existing token can therefore show missing when selected. See init_reviewer_credential_status.go:43 and init_reviewer_credential_status.go:183. Build statuses for credential-bearing selectable reviewer refs under the current profile’s resolved backend, or lazily inspect the selected ref with the same safe backend-failure behavior.

Minor: The contract docs omit the implemented optional state even though the PR verification and code use it for an unset optional GitHub App installation id. Update docs/init-ux-contract.md:172 to include optional or optional not set.

@rianjs

rianjs commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Findings

Major: The fallback status path can still report missing without checking the backend for refs that are not precomputed. initReviewerCredentialStatusForSelectionRef falls back to synthesizeReviewerCredentialStatus, and that function assigns required keys to missing purely from key specs. This affects new template refs and typed custom refs. For example, a profile with no reviewer config but an existing codereview/work-reviewer PAT in the backend will still show git_token: missing when selecting the PAT template unless that ref was already part of the active plan or a configured selectable entity. That violates the ticket’s state semantics: missing means “neither present in backend nor staged,” so the backend has to be consulted before claiming it. See init_reviewer_credential_status.go:205 and init_reviewer_credential_status.go:234.

Suggested fix: include current template/current input refs in the backend-inspected status set, or make the fallback query the resolved backend for the current ref. If backend lookup is not possible, render status unavailable rather than missing. Add a regression with no reviewer config, a seeded standard PAT reviewer token, selecting the PAT template, and expecting git_token: existing.

The previous issues around stale status after changing the secret-location field and inactive selectable reviewer entities with explicit refs look addressed.

@rianjs

rianjs commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

No findings.

The updated diff aligns with #348 and preserves the #347 draft/write boundary: backend access is read-only for status, staged secret values remain draft-local, unknown typed refs no longer falsely claim missing, and the standard template refs are now backend-inspected. The added regressions cover the prior architectural gaps, and the docs now match the implemented optional state. Richer backend destination copy can remain for #349.

@rianjs

rianjs commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

The new coverage is solid on the happy path, but I see 3 remaining gaps where a regression could still slip through.

  • Minor: the layout assertions do not prove the credential status block stays below the secret-location field, which is part of the contract. Both linear-flow tests only check that the text exists, and their order checks stop before the status field, so a misplaced section could still pass. See credentialcmd_test.go and credentialcmd_test.go.

  • Minor: re-entry coverage only exercises the same staged GitHub App reviewer coming back with staged/skipped state. It does not prove the stale state is filtered out when the reviewer credential ref or auth mode changes, which the ticket explicitly requires. A bad implementation could keep github_app_* rows visible after switching to PAT and still pass the current tests. See credentialcmd_test.go and credentialcmd_test.go.

  • Minor: TestInitInteractiveMenuFocusedReviewerEntityLabelOnlySaveSkipsCredentialWrite no longer asserts that openStore is skipped. It only blocks SetBundle, so an implementation that needlessly opens the credential backend during a label-only edit would still pass. See credentialcmd_test.go.

monit-reviewer
monit-reviewer previously approved these changes Jun 18, 2026

@monit-reviewer monit-reviewer 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.

Automated PR Review

Reviewed commit: d96f21c

Approved with 4 non-blocking suggestions below. Address at your discretion.

Summary

Reviewer Findings
harness-engineering:harness-knowledge-reviewer 1
harness-engineering:harness-self-documenting-code-reviewer 3
harness-engineering:harness-knowledge-reviewer (1 findings)

💡 Suggestion - docs/init-ux-contract.md:162

The credential status section enumerates the seven possible key states but does not define their preconditions. The PR discussion established a critical invariant: missing must only be reported after consulting the backend (meaning not present in backend AND not staged), and status unavailable is the fallback when the backend is unreachable. Without these definitions, a future implementer adding a new status path could regress to falsely claiming missing without a backend check — the exact architectural gap this PR fixes. One sentence per state defining its precondition would make the contract self-contained.

harness-engineering:harness-self-documenting-code-reviewer (3 findings)

💡 Suggestion - internal/cmd/credentialcmd/init_reviewer_credential_status.go:229

The variable ref is reassigned across three potential sources (user-supplied reviewerSecretLocation → seed fallback → standard ref) without communicating the resolution intent. A name like effectiveCredentialRef would make the fallback priority chain readable at a glance rather than requiring readers to trace through the conditionals.

💡 Suggestion - internal/cmd/credentialcmd/init_reviewer_credential_status.go:258

if ctx.ReviewerCredentialStatuses != nil encodes a non-obvious domain contract: nil means statuses were never computed (speculate with missing), while non-nil-but-no-match means the backend was consulted and does not know this ref (report unavailable). This distinction is load-bearing — it is the exact invariant the PR enforces — but a bare nil check does not surface it. Extracting backendStatusesWereComputed := ctx.ReviewerCredentialStatuses != nil would make the intent explicit.

💡 Suggestion - internal/cmd/credentialcmd/init_reviewer_credential_status.go:328

store := strings.TrimSpace(status.SecretsProfile.DisplayName()) assigns a display-name string to a variable named store, which throughout this package refers to an initStore instance. A reader scanning this function will expect a credential store object. displayName or storeLabel would eliminate the naming collision and match what the variable actually holds.

4 PR discussion threads considered.


Completed in 4m 09s | $2.40 | sonnet | daemon 0.2.132 | Glorfindel
Field Value
Model sonnet
Reviewers hybrid-synthesis, documentation:docs-reviewer, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer, harness-engineering:harness-self-documenting-code-reviewer, security:security-code-auditor
Engine claude · sonnet
Reviewed by pr-review-daemon · monit-pr-reviewer
Duration 4m 09s wall · 4m 06s compute (Reviewers: 3m 21s · Synthesis: 45s)
Cost $2.40 (estimated)
Tokens 424.8k in / 26.9k out
Turns 14

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost
hybrid-synthesis sonnet 37.8k 1.9k 13.8k 24.0k (1h) $0.18
documentation:docs-reviewer sonnet 34.6k 1.5k 13.8k 20.8k (1h) $0.15
harness-engineering:harness-architecture-reviewer sonnet 66.6k 753 13.8k 52.8k (1h) $0.33
harness-engineering:harness-enforcement-reviewer sonnet 72.7k 6.5k 13.8k 58.9k (1h) $0.46
harness-engineering:harness-knowledge-reviewer sonnet 68.0k 1.9k 13.8k 54.2k (1h) $0.36
harness-engineering:harness-self-documenting-code-reviewer sonnet 78.7k 12.8k 13.8k 65.0k (1h) $0.59
security:security-code-auditor sonnet 66.4k 1.4k 13.8k 52.6k (1h) $0.34

Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.

Comment thread docs/init-ux-contract.md
@@ -162,6 +162,25 @@ choosing **Commit staged changes and exit**, any pending secret values remain
draft-only and the session returns to a no-write state. Until final commit

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 credential status section enumerates the seven possible key states but does not define their preconditions. The PR discussion established a critical invariant: missing must only be reported after consulting the backend (meaning not present in backend AND not staged), and status unavailable is the fallback when the backend is unreachable. Without these definitions, a future implementer adding a new status path could regress to falsely claiming missing without a backend check — the exact architectural gap this PR fixes. One sentence per state defining its precondition would make the contract self-contained.

Reply to this thread when addressed.

}
ref := strings.TrimSpace(reviewerSecretLocation)
if ref == "" {
ref = strings.TrimSpace(state.seed.ReviewerCredentialRef)

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-self-documenting-code-reviewer): The variable ref is reassigned across three potential sources (user-supplied reviewerSecretLocation → seed fallback → standard ref) without communicating the resolution intent. A name like effectiveCredentialRef would make the fallback priority chain readable at a glance rather than requiring readers to trace through the conditionals.

Reply to this thread when addressed.

status := initReviewerCredentialStatus{Ref: ref}
if ctx.ExistingProfile != nil {
if resolved, err := credentials.ResolveSecretsProfileForProfile(ctx.ExistingConfig, *ctx.ExistingProfile); err == nil {
status.SecretsProfile = resolved

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-self-documenting-code-reviewer): if ctx.ReviewerCredentialStatuses != nil encodes a non-obvious domain contract: nil means statuses were never computed (speculate with missing), while non-nil-but-no-match means the backend was consulted and does not know this ref (report unavailable). This distinction is load-bearing — it is the exact invariant the PR enforces — but a bare nil check does not surface it. Extracting backendStatusesWereComputed := ctx.ReviewerCredentialStatuses != nil would make the intent explicit.

Reply to this thread when addressed.

return fmt.Sprintf("%s via %s", ref, store)
case backend != "":
return fmt.Sprintf("%s via %s", ref, backend)
default:

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-self-documenting-code-reviewer): store := strings.TrimSpace(status.SecretsProfile.DisplayName()) assigns a display-name string to a variable named store, which throughout this package refers to an initStore instance. A reader scanning this function will expect a credential store object. displayName or storeLabel would eliminate the naming collision and match what the variable actually holds.

Reply to this thread when addressed.

@rianjs

rianjs commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

No findings.

The daemon changes are additive and aligned with #348: the docs now clarify each status meaning, tests cover ordering and auth-mode filtering, and the code changes are naming/readability only. I don’t see architectural drift from the approved shape: status remains per key, draft-aware, backend-inspected where safe, unavailable when not inspected, and non-secret.

@monit-reviewer monit-reviewer 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.

Automated PR Review

Reviewed commit: 5448c0b | Previous: d96f21c (incremental)

Summary

No issues found.

9 PR discussion threads considered.


Completed in 1m 11s | $5.93 | sonnet | daemon 0.2.132 | Glorfindel
Field Value
Model sonnet
Mode Re-review · Cycle 2 · Session resumed
Reviewers hybrid-synthesis, documentation:docs-reviewer, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer, harness-engineering:harness-self-documenting-code-reviewer, security:security-code-auditor
Engine claude · sonnet
Reviewed by pr-review-daemon · monit-pr-reviewer
Duration 1m 11s wall · 1m 00s compute (Reviewers: 39s · Synthesis: 22s)
Cost $5.93 (estimated)
Tokens 1095.8k in / 31.8k out
Turns 28

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost
hybrid-synthesis sonnet 86.5k 2.9k 28.2k 58.3k (1h) $0.40
documentation:docs-reviewer sonnet 78.0k 2.2k 28.2k 49.8k (1h) $0.34
harness-engineering:harness-architecture-reviewer sonnet 174.4k 937 28.2k 146.2k (1h) $0.90
harness-engineering:harness-enforcement-reviewer sonnet 194.0k 7.9k 28.2k 165.8k (1h) $1.12
harness-engineering:harness-knowledge-reviewer sonnet 179.3k 2.9k 28.2k 151.1k (1h) $0.96
harness-engineering:harness-self-documenting-code-reviewer sonnet 210.5k 13.4k 28.2k 182.3k (1h) $1.30
security:security-code-auditor sonnet 173.1k 1.6k 28.2k 144.9k (1h) $0.90

Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.

@rianjs

rianjs commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

No findings.

The post-daemon diff is still aligned with #348. The added docs clarify the state contract, the test additions strengthen mode filtering and UI ordering, and the implementation still preserves the intended architecture: per-key non-secret status, backend-inspected missing only when safe, status unavailable for uninspected refs, and no credential writes before commit.

@rianjs rianjs merged commit c23e913 into main Jun 18, 2026
10 checks passed
@rianjs rianjs deleted the feat/348-reviewer-credential-status branch June 18, 2026 21:57
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.

Show draft reviewer credential status per key in cr init

2 participants