Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
06b701f
Add design doc for shipping zone images from monorepo
nshalman Mar 26, 2026
adb8bce
Add reference repos, exclusions, and TODOs to zone image design doc
nshalman Mar 26, 2026
ccc8cd6
Add triton-api service and zone image build infrastructure
nshalman Mar 26, 2026
e56376d
Fix setup.sh: add execute bit and use SMF_EXIT_OK
nshalman Apr 6, 2026
92c3bba
Add guards for silent Make $(shell) failures in image.defs.mk
nshalman Apr 6, 2026
3c4e12e
Fix design doc: variable names, numbering, stale references
nshalman Apr 6, 2026
fc131e0
Close bead j8g4: template READMEs not in repo
nshalman Apr 6, 2026
ae5a163
Improve PingResponse: add fields, types module, crate docs
nshalman Apr 6, 2026
5435ded
Register triton-api in openapi-manager
nshalman Apr 6, 2026
524604e
Add TODO for hardcoded bind address in triton-api-server
nshalman Apr 6, 2026
fbf908e
Scope image build .gitignore patterns to images/ directory
nshalman Apr 6, 2026
9889348
Add tritonadm CLI scaffold and design doc
nshalman Apr 6, 2026
f3785ce
Update tritonadm API client strategy: full traits, not hand-written
nshalman Apr 6, 2026
4f5fe2e
Update tritonadm priority: portal API clients unlock read-only commands
nshalman Apr 6, 2026
b9ed451
Remove reference to closed repo in tritonadm design doc
nshalman Apr 6, 2026
65ba174
Reorder tritonadm targets: grafana first, then portal
nshalman Apr 6, 2026
8d99818
Add sapi conversion plan (Phase 1)
nshalman Apr 6, 2026
43b0f65
Add sapi API trait (Phase 2)
nshalman Apr 6, 2026
d5725dd
Add sapi client library (Phase 3)
nshalman Apr 6, 2026
50c6539
Add sapi CLI (Phase 4)
nshalman Apr 6, 2026
6a1fe6b
Add sapi validation report (Phase 5 - conversion complete)
nshalman Apr 6, 2026
6111ed3
Patch SAPI OpenAPI spec to match Node.js wire format
nshalman Apr 6, 2026
c2229a4
Fix SAPI wire format: types, status codes, and remaining patches
nshalman Apr 6, 2026
a8fe3a6
Clean up SAPI types: add StorageType enum, remove ModeResponse
nshalman Apr 6, 2026
2ef5aea
Add SAPI patched validation v3 report (clean)
nshalman Apr 6, 2026
774118b
Add SAPI patched validation v2 report
nshalman Apr 6, 2026
54f26c5
Improve restify-conversion skill based on SAPI lessons
nshalman Apr 6, 2026
4b2c712
Wire SAPI and VMAPI clients into tritonadm services/instances commands
nshalman Apr 6, 2026
96e6d7d
Auto-discover SAPI/VMAPI URLs from Triton headnode config
nshalman Apr 6, 2026
544113f
Extract triton-tls crate for portable TLS cert loading on illumos
nshalman Apr 6, 2026
c9b2665
Add imgapi conversion plan (Phase 1)
nshalman Apr 6, 2026
9da0961
Add imgapi API trait (Phase 2)
nshalman Apr 6, 2026
0d77f67
Add imgapi client library (Phase 3)
nshalman Apr 6, 2026
5a6fbf9
Add imgapi CLI (Phase 4)
nshalman Apr 6, 2026
5a202c2
Add imgapi validation report (Phase 5 - conversion complete)
nshalman Apr 6, 2026
6fc441a
Implement tritonadm avail command using IMGAPI client
nshalman Apr 6, 2026
d0f4d82
Add papi conversion plan (Phase 1)
nshalman Apr 6, 2026
1758d7f
Add papi API trait (Phase 2)
nshalman Apr 6, 2026
b6ee6e7
Add papi client library (Phase 3)
nshalman Apr 6, 2026
c2edecc
Add papi CLI (Phase 4)
nshalman Apr 6, 2026
1dbf88a
Add papi validation report (Phase 5 - conversion complete)
nshalman Apr 6, 2026
944e1a5
Add napi conversion plan (Phase 1)
nshalman Apr 6, 2026
18b2943
Add napi API trait (Phase 2)
nshalman Apr 6, 2026
d3b967e
Add napi client library (Phase 3)
nshalman Apr 6, 2026
06357ad
Add napi CLI (Phase 4)
nshalman Apr 7, 2026
3c81547
Add napi validation report (Phase 5 - conversion complete)
nshalman Apr 7, 2026
1589d8d
Implement tritonadm dc-maint status command
nshalman Apr 7, 2026
ef10ea5
Implement tritonadm post-setup grafana command
nshalman Apr 7, 2026
ebd214b
Add updates server image fetching to post-setup grafana
nshalman Apr 7, 2026
ed9527c
Fix IMGAPI import-remote: use query params not body
nshalman Apr 7, 2026
6f21b9e
Implement tritonadm post-setup common-external-nics
nshalman Apr 7, 2026
2eba57d
Fix common-external-nics: set primary flag on added NIC
nshalman Apr 7, 2026
a81f3df
Add tritonadm dev commands for teardown during development
nshalman Apr 7, 2026
f1811fc
Patch IMGAPI error schema and fix import polling
nshalman Apr 7, 2026
6d82abd
Patch error schema for all Node.js Triton API clients
nshalman Apr 7, 2026
11a51ab
Add tritonadm image subcommand group (from imgapi-cli)
nshalman Apr 7, 2026
67a82cc
Remove imgapi-cli crate (consolidated into tritonadm)
nshalman Apr 7, 2026
26f2606
Add post-setup portal and refactor into shared add_service pattern
nshalman Apr 7, 2026
c35d187
Add tritonadm image import convenience command
nshalman Apr 7, 2026
7bcf38e
Add list alias and sdc-imgadm output format for image list
nshalman Apr 7, 2026
6a268ae
Fix IMGAPI TypedClient: send action as query parameter
nshalman Apr 7, 2026
4055873
Fix image import: send raw manifest JSON to preserve all fields
nshalman Apr 7, 2026
0042414
Auto-import origin images and consolidate updates URL constant
nshalman Apr 7, 2026
044f829
Fix import-remote: source and skip_owner_check as query params
nshalman Apr 7, 2026
48bd3f8
Extract compression from manifest for image file upload
nshalman Apr 7, 2026
ed32a5c
Generalize dev remove commands: add remove-portal and remove-service
nshalman Apr 7, 2026
ff5af78
Add delete alias for delete-image in tritonadm image
nshalman Apr 7, 2026
db515e8
Add portal-specific SAPI metadata for config-agent
nshalman Apr 7, 2026
df4eb27
Set USER_PORTAL_SDC_KEY in SAPI metadata for portal setup
nshalman Apr 7, 2026
971a30b
Normalize PEM data before parsing to tolerate surrounding content
nshalman Apr 7, 2026
cfe8b54
Implement tritonadm post-setup cloudapi
nshalman Apr 7, 2026
0ddc921
Add tritonadm dev remove-cloudapi command
nshalman Apr 7, 2026
640fb86
make format
nshalman Apr 7, 2026
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
555 changes: 284 additions & 271 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions .claude/skills/restify-conversion/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ git add apis/<service>-api/ openapi-specs/generated/<service>-api.json conversio
git commit -m "Add <service> API trait (Phase 2)"
```

### Step 2b: Apply OpenAPI Spec Patches (if needed)

If the Phase 1 plan identified endpoints needing patches (bare string responses,
empty bodies, status code mismatches), apply them now — BEFORE generating the client.

1. Add transform functions to `openapi-manager/src/transforms.rs`
2. Point the client at the patched spec in `client-generator/src/main.rs`
3. Run `make openapi-generate` to create the patched spec
4. Commit the patches:
```bash
git add openapi-manager/src/transforms.rs openapi-specs/patched/<service>-api.json client-generator/src/main.rs
git commit -m "Add <service> OpenAPI spec patches for wire format compatibility"
```

See `openapi-manager/src/transforms.rs` for existing examples (cloudapi, sapi).

### Step 3: Spawn Phase 3 (Generate Client)

```
Expand Down
73 changes: 73 additions & 0 deletions .claude/skills/restify-conversion/phase1-analyze.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,64 @@ For each endpoint, record:
- `res.json([...])` or `res.send(array)` → `Vec<T>`
- `res.json({key: value, ...})` → `HashMap<String, T>` or custom struct

### 3b. Identify Enum Opportunities

Search for fields that should be enums rather than strings. Where to look:

1. Conditional string comparisons — `if (x === 'foo')` or `switch(x)` on a field value
means the field has a fixed set of values → enum.
2. Constructor names — `constructor.name` returns a class name from a fixed set
(e.g., storage backends, handler types). Map each class to an enum variant.
3. Conditional `res.send()` with different strings — e.g., `res.send(flag ? 'proto' : 'full')`
means the response is a fixed set of strings → enum.
4. Internal `require()` dispatching — when code picks between N implementations,
the selector is usually a string from a fixed set.
5. Fields that shadow an enum from another type — e.g., if a response includes a `mode`
field and there's already a Mode type, the response should use the typed enum, not String.
6. Bunyan/restify internals — `log.level()` returns an integer, not a string.
Check actual return types, don't assume String.

For each enum found, document in the plan:
- Field name and where it appears
- All known variant values (wire-format strings)
- Whether it needs `#[serde(other)] Unknown` (yes for any server-controlled state field)

### 3c. Catalog Restify Response Patterns (Wire Format)

Restify has response patterns that don't map directly to Dropshot. Catalog every
response call in the endpoint handlers to catch these early:

| Restify Pattern | Wire Behavior | Dropshot Mapping |
|----------------|---------------|------------------|
| `res.send(obj)` (no status) | 200 + JSON body | `HttpResponseOk<T>` (NOT `HttpResponseCreated`) |
| `res.send(201, obj)` | 201 + JSON body | `HttpResponseCreated<T>` |
| `res.send(204)` | 204 no content | `HttpResponseUpdatedNoContent` |
| `res.send()` (no args) | 200 empty body | Needs patch: remove content from 200 response |
| `res.send('string')` | 200 + bare text | Needs patch: change schema to plain string |
| `res.send(cond ? 200 : 500, obj)` | Variable status + same body | Progenitor limitation: can't have multiple body types |

For each endpoint, record:
- The exact `res.send(...)` call and its arguments
- Whether the response needs OpenAPI spec patching
- Whether Progenitor will have trouble generating a usable client

Flag endpoints that will need patching in a "Patch Requirements" section of the plan.
The orchestrator can then create the patched spec and point the client at it.

### 3d. Catalog All Request Body Fields

Node.js handlers often accept more fields than are documented. Search for all
`req.params.*` and `req.body.*` access patterns in each handler, not just the ones
that appear required.

Common hidden optional fields:
- `uuid` — many create endpoints accept a caller-provided UUID
- `master` — flag for records replicated from remote datacenters
- `owner_uuid` — sometimes optional on creates

For each create endpoint, verify the complete set of accepted fields by reading
the handler and the model layer it calls.

### 4. Identify Route Conflicts

**CRITICAL:** Check for routes that will conflict in Dropshot.
Expand Down Expand Up @@ -235,6 +293,17 @@ apis/<service>-api/src/
└── <modules>
```

## Enum Opportunities
- <list fields that should be enums with their variant values>
- Example: `PingResponse.mode` → `SapiMode { Proto, Full }`
- Example: `PingResponse.stor_type` → `StorageType { LocalStorage, MorayStorage, ... }`

## Patch Requirements
- <list endpoints needing OpenAPI spec patching>
- Example: `GET /mode` returns bare string, needs schema patch
- Example: `POST /mode` returns 204, trait uses HttpResponseUpdatedNoContent
- Example: `POST /loglevel` returns empty 200, needs content removal patch

## Types to Define
- <list major request/response types>

Expand Down Expand Up @@ -264,6 +333,10 @@ Phase 1 is complete when:
- [ ] Response types verified (array vs map for each list endpoint)
- [ ] Field casing verified from translate() functions for every multi-word field
- [ ] Field naming exceptions documented
- [ ] Enum opportunities identified (string fields with fixed value sets)
- [ ] Restify response patterns cataloged (bare strings, 204s, empty 200s, variable status)
- [ ] Patch requirements documented (endpoints needing OpenAPI spec patching)
- [ ] All request body fields captured (including hidden optional fields like uuid, master)
- [ ] File structure planned
- [ ] Plan file written to `conversion-plans/<service>/plan.md`

Expand Down
46 changes: 45 additions & 1 deletion .claude/skills/restify-conversion/phase2-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,46 @@ This pattern is mandatory for any enum that represents server-side state. See

**Don't assume camelCase everywhere.** Check the plan's "Field Naming Exceptions" section for fields that use snake_case or other conventions in the actual API.

### 3b. Create Action-Specific Request Types (CRITICAL)
### 3b. Use Enums From Phase 1 Plan

Check the plan's "Enum Opportunities" section and create typed enums for every
identified field. Common patterns:

- Response fields with known value sets → enum on the response struct field
- Response fields that echo a request enum → reuse the same enum type
- `constructor.name` values → enum with variants matching the class names exactly
- Boolean-like string fields (`"proto"` / `"full"`) → enum

Every enum should have `#[serde(other)] Unknown` unless the set of values is
truly fixed and controlled by the client (like `UpdateAction`).

Cross-reference response structs against request enums. If `PingResponse` has a
`mode` field and `SetModeBody` uses a `SapiMode` enum, the response should use the
same `SapiMode` type — not `String`.

### 3c. Match Restify Response Patterns to Dropshot Types

Check the plan's "Patch Requirements" section and choose the right Dropshot return
type for each endpoint:

| Plan says | Dropshot return type | Needs patch? |
|-----------|---------------------|-------------|
| `res.send(obj)` (default 200) | `HttpResponseOk<T>` | No |
| `res.send(201, obj)` | `HttpResponseCreated<T>` | No |
| `res.send(204)` | `HttpResponseUpdatedNoContent` | No |
| `res.send()` (empty 200) | `HttpResponseOk<T>` | Yes: remove content from 200 |
| `res.send('string')` (bare text) | `HttpResponseOk<SomeEnum>` | Yes: replace schema with `{"type":"string"}` |
| `res.send(cond ? 200 : 500, obj)` | `HttpResponseOk<T>` | Document limitation |

Restify's default status for `res.send(obj)` is 200, not 201. Use `HttpResponseOk`,
not `HttpResponseCreated`, for create endpoints unless the Node.js code explicitly
sets status 201.

Don't create wrapper types just for Dropshot. If an endpoint returns a bare string
that maps to an existing enum, use the enum directly as the return type and patch the
spec to a string afterward. This avoids dead types like `ModeResponse`.

### 3d. Create Action-Specific Request Types (CRITICAL)

For action dispatch endpoints, create a **separate typed struct for each action**:

Expand Down Expand Up @@ -264,8 +303,13 @@ Add to `conversion-plans/<service>/plan.md`:
Phase 2 is complete when:
- [ ] API crate structure created
- [ ] All type modules implemented
- [ ] Every enum from Phase 1 plan created (check "Enum Opportunities" section)
- [ ] Response structs use typed enums (not String) for fields with known value sets
- [ ] Dropshot return types match Restify patterns (200 not 201 for default `res.send(obj)`)
- [ ] No unnecessary wrapper types (don't create FooResponse just for Dropshot)
- [ ] **Every action has a dedicated typed request struct** (check plan's action dispatch table)
- [ ] Action-specific optional fields captured (idempotent, sync, signal, etc.)
- [ ] All create body fields included (including hidden optional fields like uuid, master)
- [ ] Field naming exceptions from plan applied (snake_case fields, hyphenated names)
- [ ] WebSocket/channel endpoints implemented (check plan)
- [ ] API trait with all endpoints implemented
Expand Down
25 changes: 24 additions & 1 deletion .claude/skills/restify-conversion/phase5-validate.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,25 @@ Compare CLI commands against API endpoints:
- [ ] Action-dispatch endpoints have individual action commands
- [ ] Nested resources have appropriate subcommands

### 5. Behavioral Analysis
### 5. Enum and Wire Format Validation

Check every enum variant against the Node.js source:
- Verify variant wire-format strings match the actual values the server sends
- For `constructor.name`-based enums, verify against the actual function/class names
- For conditional string responses, verify all branches are covered

Check every response for wire format match:
- Compare `res.send()` arguments against OpenAPI spec response schema
- Verify status codes: Restify defaults to 200 for `res.send(obj)` — spec should say 200, not 201
- Verify bare string responses are patched (not wrapped in an object)
- Verify empty responses (204, empty 200) have content removed from spec
- Verify no dead/unused schemas remain in the (patched) spec

Check for missing enum opportunities:
- Search for any remaining String fields in response types that only take a fixed set of values
- Look for `if (field === 'value')` patterns in the Node.js source for fields still typed as String

### 6. Behavioral Analysis

Review handler implementations for behaviors that may need special handling:

Expand Down Expand Up @@ -218,6 +236,11 @@ Create `conversion-plans/<service>/validation.md`:
Phase 5 is complete when:
- [ ] All endpoints compared
- [ ] Type coverage analyzed
- [ ] Enum variant wire values verified against Node.js source
- [ ] Response status codes verified (especially create endpoints: 200 vs 201)
- [ ] Bare string / empty body responses verified (patched correctly in spec)
- [ ] No dead schemas in the patched spec
- [ ] No remaining String fields that should be enums
- [ ] Route conflict resolutions verified
- [ ] CLI commands verified
- [ ] Behavioral notes documented
Expand Down
40 changes: 35 additions & 5 deletions .claude/skills/restify-conversion/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,24 @@ The variable name varies (`server`, `http`, `sapi`, etc.) but all map the same w
- No params → Just `RequestContext<Self::Context>`

### Response Types
- JSON response (always 200) → `Result<HttpResponseOk<T>, HttpError>`
- Created (201) → `Result<HttpResponseCreated<T>, HttpError>`
- No content (204) → `Result<HttpResponseDeleted, HttpError>`
- HTML response → `Result<Response<Body>, HttpError>`
- Variable status code (e.g., health checks) → `Result<Response<Body>, HttpError>`

Restify defaults to 200 for `res.send(obj)`. Do not use `HttpResponseCreated`
(201) for create endpoints unless the Node.js code explicitly sets status 201.

| Restify Call | Status | Dropshot Type | Needs Patch? |
|-------------|--------|---------------|-------------|
| `res.send(obj)` | 200 | `HttpResponseOk<T>` | No |
| `res.send(201, obj)` or `res.status(201).send(obj)` | 201 | `HttpResponseCreated<T>` | No |
| `res.send(204)` | 204 | `HttpResponseUpdatedNoContent` | No |
| `res.send()` (no args) | 200 | `HttpResponseOk<T>` | Yes: remove content from 200 |
| `res.send('bare string')` | 200 | `HttpResponseOk<EnumOrType>` | Yes: replace schema with `{"type":"string"}` |
| `res.send(cond ? 200 : 500, obj)` | Variable | `HttpResponseOk<T>` | Document limitation |
| HTML response | 200 | `Result<Response<Body>, HttpError>` | N/A |

When an endpoint needs patching, the trait should use the closest valid Dropshot type
(e.g., `HttpResponseOk<SapiMode>` for a bare-string mode endpoint), and the OpenAPI spec
transform will adjust the schema to match the actual wire format. Don't create wrapper
types just to satisfy Dropshot — they become dead code after patching.

### Type Mapping (JS → Rust)
- `string` → `String`
Expand Down Expand Up @@ -655,6 +668,23 @@ These patterns prevent the most common type-safety issues in generated CLI code:

**Rule of thumb:** If a type exists in the API crate or is generated by Progenitor, import it — never redefine it.

## Progenitor Limitations

Be aware of these Progenitor limitations that affect client generation:

1. **Multiple response body types** — Progenitor panics if an endpoint has multiple
success response schemas (e.g., 200 returns `PingResponse` AND 500 returns `PingResponse`
with different semantics). The 4XX/5XX responses must reference the generic Error schema
or be omitted. Document any endpoints where the server returns a non-Error body on 5XX.

2. **`text/plain` responses** — Progenitor generates `ResponseValue<ByteStream>` for
text/plain content, which requires stream collection in the CLI. Prefer keeping
`application/json` with `{"type":"string"}` schema — this generates `ResponseValue<String>`.

3. **Empty response bodies** — For 200 responses with no content, remove the `content`
field from the spec. Progenitor generates `ResponseValue<()>` which is easy to use.
For 204, use `HttpResponseUpdatedNoContent` in the trait — Progenitor handles this natively.

## Checklist

Before completing any phase, verify:
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
/tritonadm
/cache
/rust
.env
Expand All @@ -9,3 +10,9 @@

# Test configuration (contains credentials)
cli/triton-cli/tests/config.json

# Image build artifacts (eng buildimage/release targets)
images/*/bits/
images/*/proto/
images/*/make_stamps/
images/*/*.tar.gz
Loading
Loading