feat(cli): name each remote handle in the deployment-filtered status list - #1062
Conversation
…ion echo Operation-scoped remote dispatches now share one idempotency key per deployment and generation instead of minting one key per operation, so a deployment's sibling operations land on a single data-plane apply — the first dispatch creates it and each sibling attaches its own operation. Because the shared apply answers many operations under one key, an accepted response is only trusted when it echoes the operation key the request's shape derives to; a response without the right echo (most often a data plane that predates sibling-operation attach and would alias every sibling to the first operation) is refused, the dispatch fails closed, and a counter fires for the operator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All operations of a deployment attach into the deployment's single data-plane apply, so they all record the same remote apply id. persistRemoteApplyID now fails closed when a dispatch result would give a deployment a second remote apply, DeploymentRemoteApplyID resolves the shared id for read paths, and the refusal is countable via schemabot.remote_apply_deployment_id_conflict_total. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ply id A deployment applied per shard folds its operations into one status row, but the fold never carried an external id, so operators lost the data-plane apply handle exactly where the northstar promises one: one deployment, one data-plane apply. The fold now resolves the deployment's shared apply id with a strict external_id-only resolver (DeploymentExternalID) — the legacy engine resume context carrier is excluded because on locally driven operations it holds engine-owned resume state, not an apply id. Divergent ids across one deployment's operations are omitted from the response and logged server-side rather than picking one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… as APPLY ID A deployment-filtered status list is the deployment's view of each apply, and the handle its operator correlates with the data plane's own storage and logs is the data-plane apply id — not the control-plane identifier. The APPLY ID column now carries the deployment's data-plane apply id when one is recorded, falling back to the control-plane apply id when none is (not yet dispatched, locally driven, or omitted after divergence) so every row keeps a usable handle. The EXTERNAL OP ID column keeps the per-operation remote row id; outside deployment mode nothing changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…red-external-id # Conflicts: # pkg/metrics/README.md # pkg/metrics/metrics.go # pkg/tern/grpc_client.go # pkg/tern/grpc_client_test.go
…l-id' into armand/deployment-apply-id-read
… into armand/deployment-apply-id-cli
The refusal that protects an operation's already-recorded remote apply id is the same fail-closed divergence the deployment guard counts, so it now emits the conflict counter and an error log carrying the recorded and refused ids for operator correlation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l-id' into armand/deployment-apply-id-read
… into armand/deployment-apply-id-cli
…ly-id-read # Conflicts: # pkg/storage/deployment_remote_apply.go # pkg/storage/deployment_remote_apply_test.go # pkg/tern/grpc_client.go # pkg/tern/grpc_client_deployment_id_test.go
… row id from the divergence error The divergence error backs both resolvers now, so it says data-plane apply id rather than remote apply id, and it identifies the disagreeing row by its operation key, the operator-facing handle, instead of the internal numeric apply_operation id. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ly-id-read # Conflicts: # pkg/api/progress_handlers_test.go
… into armand/deployment-apply-id-cli
…ly-id-cli # Conflicts: # pkg/storage/deployment_remote_apply.go
There was a problem hiding this comment.
Pull request overview
This PR updates the CLI’s deployment-filtered status list to render the data-plane apply identifier in the APPLY ID column (falling back to the control-plane apply id when no data-plane id is recorded), and adds/adjusts tests to validate the new “one deployment, one data-plane apply” behavior across folded and per-operation rows.
Changes:
- In deployment-filtered status output,
APPLY IDnow renders the data-plane apply id when available (viastatusApplyID), whileEXTERNAL OP IDcontinues to show per-operation remote ids. - Tightens GRPC client test scaffolding to scope tasks by operation and adds a sibling-shard test asserting shared remote apply id + shared idempotency key behavior.
- Adds tests for the status endpoint fold behavior and for CLI rendering/fallback behavior in deployment mode.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| pkg/tern/grpc_client_test.go | Improves task scoping in mocks and adds a sibling-shard dispatch test to ensure a single shared remote apply id is recorded across operations. |
| pkg/cmd/internal/templates/progress.go | Changes status list rendering to use a new statusApplyID helper so deployment-filtered lists show the data-plane apply id in the APPLY ID column. |
| pkg/cmd/internal/templates/progress_states_test.go | Updates/extends rendering tests to assert deployment mode shows the data-plane apply id and falls back to control-plane id when missing. |
| pkg/api/handlers_test.go | Adds a status endpoint test verifying folded deployment rows surface the shared data-plane apply id and omit per-operation ids. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ndle The deployment-filtered APPLY ID column carries data-plane apply ids, which 'status <apply_id>' cannot look up — it resolves control-plane apply ids only. Say what the column holds and where the control-plane id lives instead of promising a drill-down the column can't feed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…us preview The deployment-filtered status preview had no row carrying a data-plane apply id, so its APPLY ID column rendered the control-plane identifier in every scenario while the footer told the operator the column holds the data-plane id. The preview now covers all three renderings a deployment list produces: a single-operation row with its data-plane apply id, a folded per-shard row with the deployment's shared one, and a row with none recorded that keeps the control-plane id as its handle.
The footer explained what the APPLY ID column holds before naming the list that feeds the drill-down, which read as two lessons for one line. Keep the opening clause every other status list already uses and add only the part a deployment-filtered operator needs: where to find the ids 'status' accepts.
The deployment-filtered list rendered the data-plane apply id in the APPLY ID column, which left the id on screen unusable with 'status <apply_id>' and needed a footer warning operators off it. Name the two remote handles instead, the way the detail views already do: EXTERNAL APPLY ID for the deployment's shared data-plane apply and EXTERNAL OP ID for the per-operation remote row. APPLY ID goes back to the control-plane id every row can be looked up by, so the footer is the plain line again. Columns now follow the data. An optional column is dropped when no row on the page fills it, so a deployment that drives its applies locally never sees a remote-id column of dashes, and a list filtered to one deployment omits DEPLOYMENT rather than repeating it on every row. Rendering the columns from one ordered list replaces the four-way printf switch, which had no room left for another shape.
|
🤖 Review findings - created by Kiran's code review agent - for schemabot/pull/1062, b767688. Verdict: 6 findings — 0 blocking, 2 non-blocking (an undocumented rendering change outside deployment mode, and a read-model clobber that can hide the new column), 4 suggestions. Non-blocking1. statusColumn{header: "EXTERNAL ID", optional: true, value: unfilteredStatusExternalID},Pre-PR the unfiltered column was unconditional ( Failure scenario: a Spirit-only fleet runs This contradicts the PR body's "Nothing changes outside deployment mode", and nothing pins either the old or the new behaviour: the only two tests that exercise this path both seed at least one populated id — 2. In deployment mode the new active.ExternalID = op.ExternalIDThis unconditionally overwrites the value set from the parent row eight lines earlier ( if !scope.usesOperationRemoteResume() {
apply.ExternalID = remoteID
return nil
}and Marked non-blocking and PLAUSIBLE rather than confirmed: the clobber is pre-existing (introduced in #566, not touched here), it was invisible before this PR because deployment mode hard-coded General suggestions3. The dash placeholder was generalised from the external-id column to every column, a second unadvertised rendering change outside deployment mode. func statusColumnValue(column statusColumn, a ActiveApplyData) string {
if value := column.value(a); value != "" {
return value
}
return "-"
}On an unfiltered page where some applies carry a deployment and some do not, the retained 4. The if task.ApplyOperationID != nil && *task.ApplyOperationID == applyOperationID {
5. The new API test is a near-verbatim copy of the adjacent fold test.
6. maxID := 8 // "APPLY ID"
maxEnv := 3 // "ENV"
maxState := 5 // "STATE"The PR's stated rationale is that "the four-way printf switch had no room left for another shape"; The one thing that could have broken, verifiedThe wholesale replacement of the four-way
The one difference the refactor introduces that is not a faithful port is finding 1. Verified correct
This review was generated by Claude Code (claude-opus-5). |
…oyment mode The unfiltered status list dropped the EXTERNAL ID column when no row had a remote id recorded, making the flag look like a no-op. The column the operator explicitly asked for now always renders, with a dash placeholder per empty row. Deployment-mode columns keep following the data, and mixed local/deployment rows now have coverage for the dash placeholder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…none A drive that is not operation-scoped records the remote apply id on the parent apply row, not the operation, so the deployment-filtered status response no longer clobbers it with an empty operation-level id. The two deployment fold tests are folded into one table test covering shared op ids, parent-row fallback (fold and single match), and single-match operation ids winning over the parent's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🤖 Addressed the findings in two new commits — ab32cd22 (CLI) and 530be273 (API):
Verification: This reply was generated by Claude Code (Claude Fable 5). |
Why this matters
An operator running a deployment-filtered status scan is asking the data plane's question: what is the control plane driving on my deployment? The handle they correlate with the data plane's own storage and logs is the data-plane apply id, but the list only ever showed the control-plane identifier, forcing a detour through detail views to find it.
What it does
EXTERNAL APPLY IDfor the deployment's shared data-plane apply,EXTERNAL OP IDfor the per-operation remote row.APPLY IDstays the control-plane id, so every id on screen can be fed straight back tostatus <apply_id>and the footer needs no qualifier.DEPLOYMENT, which every row would otherwise repeat back to the operator who named it.EXTERNAL IDcolumn always renders, dash per row without a recorded id — the operator asked for the column explicitly, and an all-dash column positively answers "nothing recorded" where a missing one is indistinguishable from the flag doing nothing.Rendering the columns from one ordered list replaces the four-way printf switch, which had no room left for another shape.
Rendered result
Same fixture data either side, from the
status_deploymentpreview.Both remote handles recorded
Before:
After:
Operations folded into one shared data-plane apply, so there is no per-operation id
Before, the per-operation column is a dash:
After, it is left out and the shared handle is named:
No data plane behind this deployment, or nothing dispatched yet
Before:
After, no remote columns at all:
How it moves us toward the northstar
One data-plane apply per deployment; operations dispatch into it.
Opened by Claude (Fable 5).