Add sc service — one verb family for databases and buckets #28

Merged
geusebio merged 2 commits from feature/sc-service-verbs into main 2026-08-16 23:19:29 +00:00
Owner

The terminal half of Grey.ooo/Someones.Computer#607: sc service create|list|show|bind|unbind|destroy, plus engines for the catalogue create
names an entry from.

The six verbs are the platform's, not this package's. Every gate — permission,
credit, catalogue, still-bound — is decided by /cli/services
(Grey.ooo/Someones.Computer#794), which the platform's MCP tools answer
identically. What lives here is how a terminal says it.

What it looks like

Driven end to end against a stub platform, so this is real output rather than a
sketch:

$ sc service engines
postgres:17   default
postgres:16
mariadb:11.8
mysql:8.4
mysql:8.0     deprecated — past end of life, plan your migration

$ sc service create postgres:17 hearth-db --org acme
  ✓ created              acme/hearth-db · postgres:17

$ sc service create mysql:8.0 legacy-db --org acme
  ! mysql:8.0            deprecated — past end of life, plan your migration
  ✓ created              acme/legacy-db · mysql:8.0

$ sc service bind hearth-db --app hearth
  ✓ DATABASE_URL pinned for the next deployment of hearth

$ sc service ls
acme/hearth-db  postgres:17  available  hearth, worker
acme/legacy-db  mysql:8.0    available  unbound

$ sc service destroy hearth-db
sc: "hearth-db" is still bound to 1 application. Unbind it first, or force this
through — retiring it cuts those applications off from their database.
Pass --force to retire it anyway

That matches the managed-services plan's own §02 mockup, including the ! line,
which is the point: the deprecation warning is issue #607's first Done when.

Three shapes worth reviewing

  • The name is always the last argument. create hearth-db takes the
    catalogue's default choice; create postgres:17 hearth-db names one. The
    argument a person always types stays in the same place.
  • A deprecated engine is created and flagged, not refused. Applications
    pinned to one have to live somewhere. The fact is the platform's warning
    field; only the terse layout is ours, because that sentence is written for a
    client with no columns.
  • A platform sentence never names one surface's escape hatch. The still-bound
    refusal reads "or force this through", and sc appends Pass --force to retire it anyway — only on that conflict, since suggesting it for a permission
    problem would be advice that cannot work. It wraps with %w, so the
    platform's *APIError stays reachable through errors.As downstream.

bind and unbind resolve the application on the same ladder sc start uses:
--app, then app: in .sc.yml, then the directory name.

Verification

Check Result
service_test.go (new) 18 tests, green — every verb, both query parameters, the JSON bodies sent, and each refusal
hack/coverage.sh internal/cli 83.1% against its 80.7% floor; every other package unchanged
GOWORK=off go build ./... && go vet ./... clean, with the vendor tree refreshed
hack/lint.sh (golangci-lint v2.6.1) 0 issues
gofmt -l . clean
End to end driven against a stub platform; output above

Merge order — this is blocked

go.mod pins a branch commit of scclient
(v0.0.0-20260816193526-05fd06b1de33), because the calls this command makes land
in Grey.ooo/someones.computer_scclient#7. Before this merges:

  1. Merge scclient#7.
  2. Re-point go.mod at the resulting main sha and re-run go mod vendor.
  3. Then this, and then the submodule pin bump in
    Grey.ooo/Someones.Computer#794, which is what actually closes #607.

Left as a draft for exactly that reason — a pin on a non-main commit must not
reach main.

The terminal half of Grey.ooo/Someones.Computer#607: `sc service create|list|show|bind|unbind|destroy`, plus `engines` for the catalogue `create` names an entry from. The six verbs are the platform's, not this package's. Every gate — permission, credit, catalogue, still-bound — is decided by `/cli/services` (Grey.ooo/Someones.Computer#794), which the platform's MCP tools answer identically. What lives here is how a terminal says it. ## What it looks like Driven end to end against a stub platform, so this is real output rather than a sketch: ```console $ sc service engines postgres:17 default postgres:16 mariadb:11.8 mysql:8.4 mysql:8.0 deprecated — past end of life, plan your migration $ sc service create postgres:17 hearth-db --org acme ✓ created acme/hearth-db · postgres:17 $ sc service create mysql:8.0 legacy-db --org acme ! mysql:8.0 deprecated — past end of life, plan your migration ✓ created acme/legacy-db · mysql:8.0 $ sc service bind hearth-db --app hearth ✓ DATABASE_URL pinned for the next deployment of hearth $ sc service ls acme/hearth-db postgres:17 available hearth, worker acme/legacy-db mysql:8.0 available unbound $ sc service destroy hearth-db sc: "hearth-db" is still bound to 1 application. Unbind it first, or force this through — retiring it cuts those applications off from their database. Pass --force to retire it anyway ``` That matches the managed-services plan's own §02 mockup, including the `!` line, which is the point: the deprecation warning is issue #607's first *Done when*. ## Three shapes worth reviewing - **The name is always the last argument.** `create hearth-db` takes the catalogue's default choice; `create postgres:17 hearth-db` names one. The argument a person always types stays in the same place. - **A deprecated engine is created and flagged, not refused.** Applications pinned to one have to live somewhere. The *fact* is the platform's `warning` field; only the terse layout is ours, because that sentence is written for a client with no columns. - **A platform sentence never names one surface's escape hatch.** The still-bound refusal reads "or force this through", and `sc` appends `Pass --force to retire it anyway` — only on that conflict, since suggesting it for a permission problem would be advice that cannot work. It wraps with `%w`, so the platform's `*APIError` stays reachable through `errors.As` downstream. `bind` and `unbind` resolve the application on the same ladder `sc start` uses: `--app`, then `app:` in `.sc.yml`, then the directory name. ## Verification | Check | Result | |---|---| | `service_test.go` (new) | 18 tests, green — every verb, both query parameters, the JSON bodies sent, and each refusal | | `hack/coverage.sh` | `internal/cli` **83.1%** against its 80.7% floor; every other package unchanged | | `GOWORK=off go build ./... && go vet ./...` | clean, with the vendor tree refreshed | | `hack/lint.sh` (golangci-lint v2.6.1) | 0 issues | | `gofmt -l .` | clean | | End to end | driven against a stub platform; output above | ## Merge order — this is blocked `go.mod` pins **a branch commit** of `scclient` (`v0.0.0-20260816193526-05fd06b1de33`), because the calls this command makes land in Grey.ooo/someones.computer_scclient#7. Before this merges: 1. Merge scclient#7. 2. Re-point `go.mod` at the resulting `main` sha and re-run `go mod vendor`. 3. Then this, and then the submodule pin bump in Grey.ooo/Someones.Computer#794, which is what actually closes #607. Left as a draft for exactly that reason — a pin on a non-`main` commit must not reach `main`.
Add sc service — one verb family for databases and buckets
Some checks failed
CI / build (pull_request) Failing after 3m25s
e731dd4ba6
`sc service create|list|show|bind|unbind|destroy`, plus `engines` for the
catalogue `create` names an entry from (Grey.ooo/Someones.Computer#607).

The six verbs are the platform's rather than this package's: every gate and
every refusal is decided by `/cli/services`, which the MCP tools answer
identically. What lives here is how a terminal says it — a column layout, a `!`
line for a deprecated engine, and the sentence a refusal turns into.

Three shapes worth naming:

- The engine is positional and optional, and the *name* is always the last
  argument — `sc service create hearth-db` takes the catalogue's default choice
  and `sc service create postgres:17 hearth-db` names one. That way the argument
  a person always types is always in the same place.
- A deprecated engine is created and flagged rather than refused: the platform
  returns a `warning`, and this draws `! mysql:8.0  deprecated — past end of
  life, plan your migration` above the result. The *fact* stays the platform's;
  only the layout is ours, because its sentence is written for a client with no
  columns.
- A platform sentence never names one surface's escape hatch, so the still-bound
  refusal reads "or force this through" and `sc` appends `Pass --force to retire
  it anyway.` — and only to that conflict, since suggesting it for a permission
  problem would be advice that cannot work.

bind and unbind resolve the application on the same ladder `sc start` uses:
--app, then `app:` in .sc.yml, then the directory name.

Verified: 18 new tests against a stub platform, covering every verb, both query
parameters, the JSON bodies sent and each refusal; `internal/cli` at 83.1%
against its 80.7% floor; `go build`, `go vet` and `gofmt -l` clean with
GOWORK=off and the vendor tree refreshed. Driven end to end against a stub
platform, and the output matches the managed-services plan's own §02 mockup.

The scclient pin is a branch commit until Grey.ooo/someones.computer_scclient#7
merges, and must be re-pointed at main before this lands.
Wrap the refusal the --force hint is appended to
All checks were successful
CI / build (pull_request) Successful in 1m22s
79b2270c09
`hack/lint.sh` caught two things about the one line that composes an error:
errorlint wanted `%w` rather than `%s`, so the platform's `*APIError` stays
reachable through `errors.As` for anything downstream, and staticcheck wanted no
trailing full stop, matching `explainToggleFailure` next door.
geusebio changed title from WIP: Add sc service — one verb family for databases and buckets to Add sc service — one verb family for databases and buckets 2026-08-16 23:18:51 +00:00
geusebio deleted branch feature/sc-service-verbs 2026-08-16 23:19:32 +00:00
Sign in to join this conversation.
No reviewers
No labels
in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/someones.computer_agent!28
No description provided.