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
4 changes: 4 additions & 0 deletions .changelog/console-screen-gpu-model.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- Fix Console bid screening GPU model filters by sending the model in the
resource attribute key with value `true`, including when overriding an SDL.
Return an empty array in JSON/YAML when no providers match, while preserving
the pretty output message. Add request and empty-output regression tests.
5 changes: 5 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,11 @@ API key, and explicit command groups keep their own transport boundaries.
This prevents each surface from acquiring its own partial version of the
same rules.

Public Console bid screening translates `--gpu-model <model>` into the resource
GPU attribute `vendor/nvidia/model/<model>=true`, matching the API's model
selector format. A model with no matching providers yields an empty list in
JSON/YAML and a human-readable message in pretty output.

A context may use both credentials. `auth-method` remains the on-disk name for
compatibility and records only which credential `akt deploy`, `akt update`,
and `akt close` prefer. `akt context create/edit --deploy-via chain|console`
Expand Down
10 changes: 9 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -2933,6 +2933,14 @@ one-shot read.
| `akt console shell <dseq> [service] [-- command...]` | `--stdin` | Interactive shell in a lease container, default `/bin/sh`; when exactly one SDL service exists it is selected automatically. Multiple services require an explicit name and list the choices. Exec is the same operation with an explicit command (JWT scopes `shell,status`). |
| `akt console screen [sdl-file]` | `--cpu`, `--memory`, `--storage`, `--gpu`, `--gpu-model`, `--count`, `--attribute`, `--signed-by`, `--reclamation-window` | Client-side bid screening from an SDL, resource flags, or both. Explicit flags override SDL-derived fields (public endpoint, no key needed). |

`console screen --gpu-model <model>` MUST encode the model in each screened
resource's `gpu.attributes` as
`{"key":"vendor/nvidia/model/<model>","value":"true"}`. This applies both to
resource-only requests and to GPU overrides of an SDL. The model name MUST NOT
be sent as the value of a `vendor/nvidia/model` attribute, which does not constrain
the API's provider selection. When no providers match, JSON and YAML output
MUST be an empty array (`[]`); pretty output retains the no-match message.

Per the positional-primary convention (§3.8), every console command takes its primary value(s) positionally; the equivalent flags remain as overrides and a positional value wins when both are given. (2026-07: the flag twins marked *disabled pending feedback* above are commented out in code for the positional-only UX trial — the positional form is the only way while the trial runs; the original flag definitions are preserved in `FEEDBACK(2026-07)` comments for restoration.) Default structured reads are indented JSON, while human acknowledgements and streams use the command-specific pretty forms described below. USD values at or above one cent render with two decimals. A nonzero sub-cent value renders with up to six decimals and trailing zeros stripped; a magnitude below one millionth of a dollar renders as `$<0.000001` (or `-$<0.000001`) rather than the false `$0.00`. Zero remains `$0.00`. State-changing calls are recorded in the context's action log as `type=console` entries (§5.6). No command ever prints a Console API key, except the one-time secret from `apikey create`.

Console escrow `uact` values are micro-USD: amounts render as dollars and
Expand Down Expand Up @@ -4947,7 +4955,7 @@ Status of `akt` coverage for every Akash Console capability. "Covered" means the
| Live lease status | `akt console status <dseq>` (`--watch`) | Reads the provider gateway directly using a Console-minted scoped JWT. |
| Container logs / cluster events | `akt console logs <dseq> [service]`, `akt console events <dseq>` (`--follow`) | Same streaming paths as `akt provider lease-logs/lease-events`, authenticated by the Console JWT — no websocket relay needed. |
| Exec / interactive shell | `akt console shell <dseq> <service> [-- command]` | Exec is the same command with an explicit command argument. |
| Bid screening | `akt console screen <sdl-file>` | Public endpoint; resources are derived from the SDL. |
| Bid screening | `akt console screen [sdl-file]` | Public endpoint; resources come from the SDL, resource flags, or both. GPU model filtering and empty output follow §2.9. |
| Wallet balances & managed wallets | `akt console wallet balance/list` | Balances are µACT rendered as USD (1 ACT = 1 USD); wallet credits are dollar-scale. |
| Auto Recharge | `akt console wallet settings [true\|false]` | Account-level card charging (`autoReloadEnabled`), the Console UI's "Auto Recharge". Distinct from per-deployment funding, which is always on and not configurable. |
| Cost estimate & usage history | `akt console wallet cost`, `akt console usage [from] [to]` | Usage totals the requested range; the lifetime figure is reported separately. |
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/console/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ func screenCmd(mgrFn func() *aktctx.Manager) *cobra.Command {
}

if len(providers) == 0 {
return printConsoleText(cmd, "No providers matched the SDL's resource requirements.\n")
return printConsoleResult(cmd, "No providers matched the SDL's resource requirements.", []console.ScreenedProvider{})
}

return printJSON(cmd, providers)
Expand Down Expand Up @@ -703,7 +703,7 @@ func screeningRequestFromResources(cmd *cobra.Command, args []string, raw json.R
}
gpuValue := map[string]any{"units": map[string]any{"val": strconv.FormatUint(uint64(gpu), 10)}}
if model != "" {
gpuValue["attributes"] = []any{map[string]any{"key": "vendor/nvidia/model", "value": model}}
gpuValue["attributes"] = []any{map[string]any{"key": "vendor/nvidia/model/" + model, "value": "true"}}
}
resourceValue["gpu"] = gpuValue
}
Expand Down
100 changes: 100 additions & 0 deletions internal/cli/console/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,106 @@ func TestDefaultShellServiceCoversManifestBoundaries(t *testing.T) {
}
}

func TestScreenGPUModelRequest(t *testing.T) {
tests := []struct {
name string
model string
useSDL bool
}{
{name: "rtx5090", model: "rtx5090"},
{name: "a100", model: "a100"},
{name: "unknown model", model: "totally-fake-gpu"},
{name: "SDL override", model: "a100", useSDL: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var req struct {
Resources []struct {
Resource struct {
GPU struct {
Units struct {
Val string `json:"val"`
} `json:"units"`
Attributes []aktconsole.Attribute `json:"attributes"`
} `json:"gpu"`
} `json:"resource"`
} `json:"resources"`
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/v1/bid-screening" {
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Errorf("decode screening request: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
writeJSON(t, w, `{"providers":[{"owner":"akash1prov"}]}`)
}))
defer srv.Close()

args := []string{"screen"}
if test.useSDL {
sdlPath := filepath.Join(t.TempDir(), "deploy.yaml")
if err := os.WriteFile(sdlPath, []byte(screenSDL), 0o600); err != nil {
t.Fatal(err)
}
args = append(args, sdlPath)
}
args = append(args, "--gpu", "3", "--gpu-model", test.model)
if _, err := execConsole(t, newTestManager(t), srv.URL, args...); err != nil {
t.Fatalf("screen: %v", err)
}
if len(req.Resources) != 1 {
t.Fatalf("resources length = %d, want 1", len(req.Resources))
}
gpu := req.Resources[0].Resource.GPU
if gpu.Units.Val != "3" {
t.Errorf("GPU units = %q, want 3", gpu.Units.Val)
}
want := []aktconsole.Attribute{{Key: "vendor/nvidia/model/" + test.model, Value: "true"}}
if !reflect.DeepEqual(gpu.Attributes, want) {
t.Errorf("GPU attributes = %#v, want %#v", gpu.Attributes, want)
}
})
}
}

func TestScreenEmptyOutput(t *testing.T) {
for _, format := range []string{"pretty", "json", "yaml"} {
t.Run(format, func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
writeJSON(t, w, `{"providers":[]}`)
}))
defer srv.Close()

out, err := execConsole(t, newTestManager(t), srv.URL,
"screen", "--gpu-model", "totally-fake-gpu", "-o", format)
if err != nil {
t.Fatalf("screen: %v", err)
}
if format == "pretty" {
if want := "No providers matched the SDL's resource requirements.\n"; out != want {
t.Errorf("empty pretty output = %q, want %q", out, want)
}
return
}
var decoded any
if format == "json" {
err = json.Unmarshal([]byte(out), &decoded)
} else {
err = yaml.Unmarshal([]byte(out), &decoded)
}
if err != nil {
t.Fatalf("decode %s output: %v", format, err)
}
if providers, ok := decoded.([]any); !ok || len(providers) != 0 {
t.Errorf("empty %s output = %#v, want an empty array", format, decoded)
}
})
}
}

func TestScreeningRequestSupportsResourceFlagsWithoutSDL(t *testing.T) {
cmd := screenCmd(func() *aktctx.Manager { return nil })
for flag, value := range map[string]string{
Expand Down
Loading