Skip to content

feat(server): say what was actually checked, and keep the binary current - #70

Merged
owjs3901 merged 1 commit into
mainfrom
feat/honest-verdicts-and-current-binary
Sep 21, 2026
Merged

owjs3901 merged 1 commit into
mainfrom
feat/honest-verdicts-and-current-binary

Conversation

@owjs3901

Copy link
Copy Markdown
Contributor

Four verdicts claimed work that had not been done, and one failure gave the caller nothing to do about it. Each section below is independent — say the word and I will split them.

devup_ui_validate reported violations a correct app cannot avoid

app/layout.tsx without a default export is a build error, so file-placement against it was advice that could not be taken — and because the finding is a warning, strict: true failed a correct Next.js application over it. not-found.tsx collected two findings for the same reason. The rule already knew layout.tsx was special one branch below; it now knows it in both, along with the rest of the segment and metadata conventions, and only inside the router directory that reserves those namessrc/components/error.tsx is still an ordinary module.

The page-name rule judged every component in a route file rather than the one the route renders, so a correct export default function NoticePage was reported for its own PageContent helper. one-component-per-file then asked for that helper to be moved somewhere the file-placement rule forbids, leaving src/components/ as the only destination for a component used by exactly one route.

The monorepo root validated one package against another's theme

theme_for_validation took the first devup.json the sorted scan found. Measured on a two-package fixture:

availableTokenCount: 1          <- apps/admin only
themeAvailable: true
themeGuardrail: null            <- no warning at all
ok: false  okReason: error-violations
  unknown-token / ERROR : "$bg is not defined in devup.json."
                          suggestion: "closest existing tokens: $adminBg"

$bg exists in apps/front. It now refuses to choose and returns candidateProjectRoots naming the projectRoot, authority and appliesTo of each — which is what devup_project_context already did for the same directory.

clean covered things that were never checked

A call with neither tsx nor files validated the empty string and answered ok: true, okReason: "clean" about code that was never sent; it is refused now. clean-unverified-tokens distinguishes "no violation found" from "no violation found, and the $token references this code makes were never checked because no theme was resolved". Tokens inside css() / globalCss() / keyframes() were invisible to the check entirely — only $-prefixed strings are inspected, so no call gains a new warning severity from this.

A long Section menu returned nothing

Refused outright when it exceeded the plugin transport — for a menu that was merely long, and retrying reproduced it because the ceiling follows the Section's size. It now surrenders text previews first, then shortens from the end under the same projectionTruncated the traversal and candidate caps already raise. allScreens still refuses a truncated index rather than pretending to be whole; silence was the thing forbidden, not truncation.

Of the twenty-five codes the plugin scripts throw, two carry a JSON payload — the rest arrived as pluginCode: null with generic advice. The code is read from the message for all of them now, and the ones naming a caller-fixable condition are mapped:

plugin code code JSON-RPC
DEVUP_NODE_NOT_FOUND · DEVUP_PAGE_NOT_FOUND DEVUP_FIGMA_NODE_NOT_FOUND -32603 → -32602
DEVUP_ENVELOPE_TOO_LARGE · DEVUP_EXPLORE_PROJECTION_TOO_LARGE DEVUP_FIGMA_RESPONSE_TOO_LARGE -32603
everything else (internal acquisition conditions) DEVUP_SNAPSHOT_UNSUPPORTED -32603

A missing node is the most common mistake there is, and the README promises the JSON-RPC code alone separates "fix the arguments" from "stop and report".

The parent's background took a second acquisition

placementContracts now carries the collected parent's fill, layout mode and name. Learning that the desktop frame behind an app screen was #E6EDE6 otherwise meant exporting that parent separately — a second Figma call for a colour the capture already held. Reported, never emitted: parentIncludedInOutput stays false.

self_update — the binary follows the current release

release_check reports a difference and deliberately never acts, for a reason about this process: a binary replaced under a running server cannot recover the stdio pipe the host already holds. That reason is untouched. Nothing here replaces the running image — the published asset is downloaded, proved by running --self-check on it, and renamed into place, which the running process cannot notice because its image was loaded at spawn.

Verified against the real 0.9.0 release with a binary built at 0.1.0:

t=10s  running=0.1.0  latest=-      nextStartDiffers=false
t=20s  running=0.1.0  latest=-      nextStartDiffers=false
t=30s  running=0.1.0  latest=0.9.0  nextStartDiffers=true  exeBytes=27,510,272
stderr: placed release 0.9.0; this process keeps running 0.1.0, the next start uses 0.9.0

SESSION 2 runs: 0.9.0          <- one restart
promoted SHA256 == published asset SHA256
exe.previous  == the displaced build, byte for byte

It stands down where it does not own its install (a package manager's directory, or one it cannot write), so a launcher that resolves @latest on every spawn is never fought. server.updateAvailable.autoUpdate reports which mechanism is in force, whether something is waiting for the next start, the asset for this platform, and installedPath — the file to replace when the automatic path is not available. Switching off DEVUP_MCP_NO_UPDATE_CHECK also reports disabled, because with no check nothing is ever discovered to stage.

Verification

  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
  • cargo test --workspace --all-features: 116 suites, 0 failures (20 new tests)
  • plugin/dist/code.js regenerated with npm run build; byte-stable on a second run
  • Release binary driven over stdio for every user-visible change, with controls proving the rules still fire where they should (src/components/Card.tsx, app/page.tsx named Home, src/components/error.tsx)

One snapshot moved: sourceParentBackground: null added beside sourceParentSize — purely additive.

Not covered by these tests

The plugin error mapping is unit-tested against upstream_error; the path from a real plugin throw is first exercised by the next release. Section truncation and the parent background are verified on synthetic scene graphs and fixtures, not against a live Figma file. self_update's download ran for real against the published 0.9.0 asset.

Four verdicts claimed work that had not been done, and one failure gave the
caller nothing to do about it.

`devup_ui_validate` reported `file-placement` against the Next.js files whose
default export the framework requires. `app/layout.tsx` without one is a build
error, so the advice could not be taken, and because the finding is a warning
it failed `strict: true` on a correct application. The rule already knew
`layout.tsx` was special one branch below; it now knows it in both, together
with the rest of the segment and metadata conventions, and only inside the
router directory that reserves those names.

The page-name rule judged every component in a route file rather than the one
the route renders, so a correct `export default function NoticePage` was
reported for its own `PageContent` helper. `one-component-per-file` then asked
for that helper to be moved somewhere the file-placement rule forbids, leaving
`src/components/` as the only destination for a component used by one route.

At a monorepo root the theme lookup took the first `devup.json` the sorted scan
found. `apps/front` code was validated against `apps/admin`'s theme and its real
tokens came back as `unknown-token` *errors*, suggesting the other package's
token as the fix. It now refuses to choose and returns `candidateProjectRoots`
naming the `projectRoot` that selects each - which is what
`devup_project_context` already did for the same directory.

A call with neither `tsx` nor `files` validated the empty string and answered
`ok: true, okReason: "clean"` about code that was never sent. It is refused
now, and `clean` is split so `clean-unverified-tokens` says a `$token`
reference existed but no theme was resolved to check it against. Tokens inside
`css()`/`globalCss()`/`keyframes()` were invisible to that check entirely, so
an unknown one there passed in silence.

A Section whose candidate menu exceeded the plugin transport was refused
outright: nothing came back for a menu that was merely long, and retrying
reproduced it because the ceiling follows the Section's size. It now surrenders
text previews first, then shortens from the end under the same
`projectionTruncated` the traversal and candidate caps already raise.
`allScreens` still refuses a truncated index rather than pretending to be whole
- silence was the thing forbidden, not truncation.

Of the twenty-five codes the plugin scripts throw, two carry a JSON payload.
The rest arrived as `pluginCode: null` with generic advice. The code is read
from the message for all of them now, and the ones naming a caller-fixable
condition are mapped: a missing node or page reached callers as `-32603
INTERNAL_ERROR`, while the README promises the JSON-RPC code alone separates
"fix the arguments" from "stop and report". That promise failed on the most
common mistake there is.

The placement contract carries the collected parent's fill, because learning
that the desktop frame behind an app screen was `#E6EDE6` otherwise took a
second Figma acquisition for a colour the capture already held.

Finally, `self_update` keeps the binary current with nobody tending it. The
release check already reported a difference and deliberately never acted, for a
reason about *this* process: a binary replaced under a running server cannot
recover the stdio pipe the host already holds. That reason is untouched.
Nothing here replaces the running image - the published asset is downloaded,
proved by running `--self-check` on it, and renamed into place, which the
running process cannot notice because its image was loaded at spawn. The next
start is the one that differs. It stands down where it does not own its install
(a package manager's directory, or one it cannot write), and
`server.updateAvailable.autoUpdate` reports which mechanism is in force and
which switch turned it off.
@owjs3901
owjs3901 merged commit 2127389 into main Sep 21, 2026
9 checks passed
@owjs3901
owjs3901 deleted the feat/honest-verdicts-and-current-binary branch September 21, 2026 13:02
Sign up for free to 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