Skip to content

fix: reconcile command-less serve shapes in applyManifest - #10

Open
m4ttheweric wants to merge 1 commit into
mainfrom
fix/serve-shape-reconcile
Open

fix: reconcile command-less serve shapes in applyManifest#10
m4ttheweric wants to merge 1 commit into
mainfrom
fix/serve-shape-reconcile

Conversation

@m4ttheweric

Copy link
Copy Markdown
Collaborator

Follow-up to #7, closing the deferred CodeRabbit finding on applyManifest.

Problem

applyManifest only reconciled an existing record's serve shape when the resolved shape carried a start command (else if (shape.command)). Two consequences:

  • A port-only app activating a port-only alt returned success but kept its base port and route ... deck alt was a silent no-op for routing.
  • A supervised app whose manifest dropped commands.start silently kept its old launchd service.

Fix

The existing-record branch now handles every resolved shape:

  • port-only (external) app, port changededitApp({ port }) propagates the new port + route alias. editApp's external path updates the portless alias without touching launchd, so deck alt actually re-routes.
  • external → service (start added) → 400 refusal. editApp keeps a record's kind, so it would set a command but never install launchd, leaving a route-only app with a command and no service. Refuse rather than half-apply; deck remove + re-register is the explicit path.
  • service → route-only (start dropped) → 400 refusal. Tearing a live service down to route-only is a structural change editApp doesn't perform; refusing beats silently keeping the stale service.

A full kind-transition capability in editApp (both directions, in place) is deliberately out of scope ... the refusals turn a silent wrong-state into a clear, actionable error, which is the actual defect.

Behavior note

External-app port changes now flow through editApp on every alt toggle / manifest edit, where they previously no-opped. External / staticPort ports are exempt from deck's port-collision check by design (they're the user's own process, not deck's to arbitrate), so two external apps can be aliased to the same port with no 409. This is consistent with the existing stance for that field, not a new arbitration; calling it out since the pathway is now reachable more often.

Testing

  • Full unit suite bun test core src = 515 pass / 0 fail (512 baseline + 3 new).
  • New tests assert real state (record kind/port/command and the edge.aliases route map), not just status codes: port-only alt re-points the route; external→service refused with kind unchanged; service→route-only refused with the service kept.

🤖 Generated with Claude Code

Previously applyManifest only synced an existing record when the resolved
shape had a start command, so a port-only app activating a port-only alt
returned success while keeping its base port/route, and a supervised app
that dropped commands.start silently kept its old service.
Now the existing-record branch handles every resolved shape:
- port-only (external) app whose port changed -> editApp propagates the new
port + route alias (editApp's external path updates the alias, no launchd).
- external -> service (start added) -> 400 refusal: editApp keeps a record's
kind, so it would set a command but never install launchd. Refuse rather
than half-apply; `deck remove` + re-register is the path.
- service -> route-only (start dropped) -> 400 refusal: tearing a live
service down to route-only is a structural change editApp does not do.
Adds 3 tests: port-only alt re-points the route (asserts edge.aliases), and
both kind transitions are refused with state left unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 49 minutes.

View limit details

Limit details: You’ve used the included review currently available. Your 74 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 423c37ce-7b82-4560-910b-d8ffc0ab2966

📥 Commits

Reviewing files that changed from the base of the PR and between 195d9a2 and 9f5e2ac.

📒 Files selected for processing (2)
  • src/api/register-manifest.test.ts
  • src/api/register-manifest.ts

Comment @coderabbitai help to get the list of available commands.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@m4ttheweric