--resource: the third name a hand-built box does not share with you #23

Merged
dan-claude-bot merged 1 commit from feat/resource-aliases into main 2026-07-13 19:02:28 +00:00
dan-claude-bot commented 2026-07-13 18:50:31 +00:00 (Migrated from github.com)

#20 shipped the refusal without shipping the resolution.

capture correctly refuses when a manifest resource does not exist on the
source — and then there was no way to say "it's over there, under another
name."
That gap was found within the hour, on the box that motivated the
whole thing.

the manifest says the box says
core Incubator Stack v2
landing Incubator Landing
postgres Incubator Database v2
redis Incubator Redis v2
umami Incubator Umami

Neither is wrong. One names things for a human reading a UI, the other for a
machine reading a diff. Neither gets to overwrite the other — so the mapping
is stated at the call site:

cast capture heavy-duty/incubator --env prod --instance box-b \
  --project Incubator --environment staging \
  --resource core="Incubator Stack v2" \
  --resource umami="Incubator Umami"

Applied at the boundary: live resources are renamed into the manifest's
vocabulary once, immediately after the lookup, so computeDiff / classify /
reconcile all keep matching by name exactly as before, and none of them needs
to know a hand-built box was ever involved.

diff needed this as much as capture did

This is the part worth reviewing carefully. Without an alias, a --full diff
against a box whose resources are named differently reports every manifest
resource as "to create"
and never mentions the live ones. That is the D-237 lie
by another route: a confident full-create plan against a box that has all of it,
running, under other names.

That diff is the staleness gate of a live migration — the last check before
the clock starts. It would have passed by reporting that production did not
exist.

apply refuses it, up front

Before a clone, a decrypt, or a single call. apply creates under the
manifest's names, so an alias there could only mean adopt the existing one
instead
— updating in place rather than creating. That is a different
operation, nobody has asked for it, and guessing would silently create a
duplicate beside the very resource the operator was pointing at.

Two smaller things, both the same principle

  • inventory, when nothing matched and yet the box is full of resources, now
    says so plainly and prints the --resource lines to paste. "The box is
    empty"
    is exactly the wrong conclusion, and it was the easy one to draw from
    the old output — which is how a full-create plan gets laundered into a pass.
  • The absent-resource refusal prints the same suggestion, one line per absent
    resource.

An alias whose left side names no manifest resource is an error, not a
no-op
— a typo would otherwise map nothing, leave the real resource looked up
under its own name, and refuse with no hint that the flag had missed.

And inventory keeps the box's own name beside ours:

on the box, and in the manifest
    application  core           ← "Incubator Stack v2" on the box
      both:          ADMIN_EMAIL, DATABASE_URL, MAILGUN_API_KEY, …
      manifest only: NODE_ENV
      box only:      ALLOW_DB_RESET, ALLOW_SEED, MAX_ROUNDS, SOURCE_COMMIT, STAGING_TOOLS

A document that renamed the box's resources into our vocabulary and then never
mentioned theirs would be unusable against the UI it describes.

That box only: line is the entire point of the verb, incidentally: on the real
box it surfaced a live config var the manifest had never declared (MAX_ROUNDS,
a round cap that defaults to 3 in code) and a retired flag still set on the box
(STAGING_TOOLS) — neither of which any other tool would have shown us.

Checks

npm run check (biome), npm run build (tsc), npm test169 passing
(was 164). New coverage: capture through an alias, inventory showing both names
and diffing the pair's keys, the nothing-matched hint, the typo refusal, and
apply refusing the flag.

🤖 Generated with Claude Code

#20 shipped the refusal without shipping the resolution. `capture` correctly refuses when a manifest resource does not exist on the source — and then there was no way to say **"it's over there, under another name."** That gap was found within the hour, on the box that motivated the whole thing. | the manifest says | the box says | | --- | --- | | `core` | `Incubator Stack v2` | | `landing` | `Incubator Landing` | | `postgres` | `Incubator Database v2` | | `redis` | `Incubator Redis v2` | | `umami` | `Incubator Umami` | Neither is wrong. One names things for a human reading a UI, the other for a machine reading a diff. **Neither gets to overwrite the other** — so the mapping is stated at the call site: ```sh cast capture heavy-duty/incubator --env prod --instance box-b \ --project Incubator --environment staging \ --resource core="Incubator Stack v2" \ --resource umami="Incubator Umami" ``` Applied **at the boundary**: live resources are renamed into the manifest's vocabulary once, immediately after the lookup, so `computeDiff` / `classify` / `reconcile` all keep matching by name exactly as before, and none of them needs to know a hand-built box was ever involved. ## `diff` needed this as much as `capture` did This is the part worth reviewing carefully. Without an alias, a `--full` diff against a box whose resources are named differently reports **every manifest resource as "to create"** and never mentions the live ones. That is the D-237 lie by another route: a confident full-create plan against a box that has all of it, running, under other names. That diff is the **staleness gate of a live migration** — the last check before the clock starts. It would have passed by reporting that production did not exist. ## `apply` refuses it, up front Before a clone, a decrypt, or a single call. `apply` **creates** under the manifest's names, so an alias there could only mean *adopt the existing one instead* — updating in place rather than creating. That is a different operation, nobody has asked for it, and guessing would silently create a duplicate beside the very resource the operator was pointing at. ## Two smaller things, both the same principle - **`inventory`, when nothing matched and yet the box is full of resources**, now says so plainly and prints the `--resource` lines to paste. *"The box is empty"* is exactly the wrong conclusion, and it was the easy one to draw from the old output — which is how a full-create plan gets laundered into a pass. - **The absent-resource refusal** prints the same suggestion, one line per absent resource. An alias whose left side names **no manifest resource is an error, not a no-op** — a typo would otherwise map nothing, leave the real resource looked up under its own name, and refuse with no hint that the flag had missed. And `inventory` keeps the box's own name beside ours: ``` on the box, and in the manifest application core ← "Incubator Stack v2" on the box both: ADMIN_EMAIL, DATABASE_URL, MAILGUN_API_KEY, … manifest only: NODE_ENV box only: ALLOW_DB_RESET, ALLOW_SEED, MAX_ROUNDS, SOURCE_COMMIT, STAGING_TOOLS ``` A document that renamed the box's resources into our vocabulary and then never mentioned theirs would be unusable against the UI it describes. That `box only:` line is the entire point of the verb, incidentally: on the real box it surfaced a live config var the manifest had never declared (`MAX_ROUNDS`, a round cap that defaults to 3 in code) and a retired flag still set on the box (`STAGING_TOOLS`) — neither of which any other tool would have shown us. ## Checks `npm run check` (biome), `npm run build` (tsc), `npm test` — **169 passing** (was 164). New coverage: capture through an alias, inventory showing both names and diffing the pair's keys, the nothing-matched hint, the typo refusal, and `apply` refusing the flag. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No reviewers
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: heavy-duty/cast#23
No description provided.