Skip to content

Document what building droidsight needs, and attest the release archives - #22

Merged
edgecasehuman merged 3 commits into
mainfrom
document-the-build-and-attest-the-release
Aug 25, 2026
Merged

edgecasehuman merged 3 commits into
mainfrom
document-the-build-and-attest-the-release

Conversation

@edgecasehuman

Copy link
Copy Markdown
Owner

What this changes

Three things a user meets before they ever reach a device: the build fails on a
clean machine for an undocumented reason, the launcher's error messages can be
lost in the one environment it runs in, and the release archives are unattested.

  • NASM is named as the build prerequisite it has always been.
    openh264-sys2 assembles Cisco's decoder with it and no platform preinstalls
    it. CI installs it as the first step of every compiling job, and those
    workflow comments were its only mention anywhere. So cargo build and
    cargo install droidsight both failed on a clean machine, inside a
    transitive dependency, where the error names nothing the reader recognizes.
    The README also documented exactly one install route; the crate has been on
    crates.io and the server in the MCP Registry since 1.0.0 and it mentioned
    neither.
  • The launcher writes its diagnostics with fs.writeSync. Every message
    was a console.error followed immediately by process.exit, and writes to
    stderr are only synchronous for a TTY or a regular file — to a pipe on POSIX
    they are queued, and process.exit does not drain the queue. An MCP client
    always gives this process a pipe, so those messages were at risk of being
    dropped in precisely the situation they exist for. EAGAIN retries, EPIPE
    stops, anything else falls back to the lossy path.
  • The release archives carry a provenance attestation, and the notes come
    from a new CHANGELOG.md instead of --generate-notes. The published hash is
    computed and published by the same workflow, so it can say a file has not
    changed since it was hashed and nothing about what produced it.

Authority and security

The attestation step adds attestations: write to the publish job, alongside
the id-token: write that npm trusted publishing already required. No new
secret: both are short-lived OIDC tokens minted per run.

No change to tool schemas, environment variables, path confinement, subprocess
construction, device selection, output limits, or protocol lifecycle.

Checks

  • cargo fmt --all -- --check
  • cargo clippy --locked --all-targets -- -D warnings
  • cargo test --locked -- --test-threads=1 — 114 passing
  • A test fails without this change, or the change is genuinely untestable
  • Cargo.lock is included, if dependencies changed — no dependency change

Beyond the Rust gates: both npm/check-*-consistency.mjs pass, the launcher
was exercised by fourteen checks through fully piped stdio (message content on
all three failure paths, argument forwarding, exit-code passthrough), and the
release workflow's own note-extraction script was executed — read out of the
YAML rather than retyped — against the real changelog, including the case where
a version has no section and the release must fail.

Two things stay unproven here and are worth saying out loud. The attestation
step only runs on a tag, so it has never executed. And stderr-to-a-pipe is
already synchronous on Windows, so a local run of the launcher proves the fix
did not regress rather than proving the POSIX case it exists for.

Device testing

Not exercised against a device. Nothing here touches the device path.

openh264-sys2 builds Cisco's H.264 decoder from C++ and assembles its hot
paths with NASM, which no platform preinstalls. CI installs it as the first
step of every job that compiles, and the release workflow does the same --
and those two comments were the only mention of it anywhere in the
repository.

So both compiling routes failed on a clean machine. `cargo build --locked
--release --bin droidsight`, which the README offered as the alternative to
npx, and `cargo install droidsight`, which is what someone arriving from
crates.io will type. Neither failure names this crate: the error comes from
a transitive dependency the reader has no reason to have heard of, which is
the hardest possible shape for a first-contact error to take.

The README also documented exactly one way to install this server. The
crate has been on crates.io and the server in the MCP Registry since 1.0.0,
and it mentioned neither. Both are now listed alongside npx, with badges,
and the npx route is marked as the one that needs no build tools at all.
Every diagnostic in cli.js was a console.error followed immediately by
process.exit. Writes to stderr are only synchronous when stderr is a TTY or
a regular file; to a pipe on POSIX they are queued, and process.exit does
not drain the queue.

An MCP client always gives this process a pipe for stderr. So the messages
explaining a missing platform package, an --omit=optional install, or a
binary that cannot be executed were at risk of being dropped in precisely
the situation they were written for -- while looking correct in every
terminal anyone would test them in.

They now go through one helper that writes with fs.writeSync, which
bypasses the queue. A non-blocking pipe can refuse the write with EAGAIN,
which means retry rather than failed, so that case loops; EPIPE means the
reader is already gone and there is nobody left to tell, so that one stops.
Anything else falls back to console.error, on the grounds that reporting
through the lossy path beats not reporting at all.

The unsupported-platform message was also pointing at `cargo install --git`
a moment before the crate existed on crates.io, and said nothing about
NASM. It now names the published crate and the prerequisite, so it does not
send someone to the failure the README was just taught to prevent.

Verified with fourteen checks against the real file through fully piped
stdio: message content on all three failure paths, argument forwarding, and
exit-code passthrough. One caveat worth recording -- stderr-to-a-pipe is
already synchronous on Windows, so a local run proves this did not regress
rather than proving the POSIX case it exists for.
The workflow said it plainly: the npm packages carry provenance
attestations, but the standalone archives are unsigned and unattested, and
a published hash is the only integrity check someone downloading a tarball
directly can perform. That hash is computed and published by this same
workflow, so it can say a file has not changed since it was hashed and
nothing at all about what produced it.

attest-build-provenance binds each archive to this workflow, this commit,
and this repository, in a store the release page cannot rewrite, checkable
with `gh attestation verify <archive> --repo edgecasehuman/droidsight`. It
runs before anything is published, like the npm preflight checks above it,
so a failure ends the run while the tag is still re-runnable.

The release notes were `--generate-notes`, which produces a list of commit
subjects. That describes the work rather than what it means for someone
deciding whether to upgrade, and it is the one thing a release page has to
carry. Notes now come from a CHANGELOG section matching the version being
built, with the checksums appended. A tag whose version has no section
fails the run rather than publishing an empty page.

The changelog itself is reconstructed from the tags and the commits, not
from memory: 1.0.0 as released, and the three post-tag registry and
provenance fixes under Unreleased.

Verified by executing the workflow's own run: script -- read out of the
YAML rather than retyped -- against the real changelog, for a version that
exists, the oldest version, which has no successor heading to stop at, and
a version that does not exist, which must fail.
@edgecasehuman
edgecasehuman merged commit acc44d9 into main Aug 25, 2026
10 checks passed
@edgecasehuman
edgecasehuman deleted the document-the-build-and-attest-the-release branch August 25, 2026 00: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