* fix: validate parsed JSON at untrusted boundaries with Valibot Clipboard HTML, library revisions from shared storage, MCP and automation WebSocket messages, the MCP discovery file, sidecar output and AI/MCP tool arguments were JSON.parse'd and cast to their expected types, so a malformed payload reached the document or crashed paste. They now go through v.pipe(v.string(), v.parseJson(), Schema), which reports bad JSON and a wrong shape as the same validation failure. The path_set tool rejects an invalid VectorNetwork and shares its parser with create_vector. The CLI library catalog validates its files and runs revisions through the same size, identity and content-hash checks as the app; reading image bytes as index-keyed records also stops them coming back empty. Hand-rolled typeof readers for plugin data, document metadata, caches and preferences become schemas with their behaviour preserved, and readCacheJSON takes a schema for its payload. open-pencil/no-unvalidated-json-parse rejects type assertions on JSON.parse results other than `as unknown` in src and packages/*/src. * refactor: validate parsed JSON in tests and tooling Extend open-pencil/no-unvalidated-json-parse beyond source: tests, helpers and repo tooling now parse JSON through Valibot schemas instead of asserting a type. The shared fixture reader returns a validated object; its old array annotation never matched the fixtures. * fix: validate clipboard geometry bytes, library images and model catalogs Clipboard geometry blobs and library image bytes must be bytes at contiguous indexes, so out-of-range or gapped values are rejected instead of silently becoming different geometry or images; serialized library nodes must carry source metadata. The models.dev and OpenRouter responses are validated like their cached copies, and activate-tab rejects a CDP frame it cannot read instead of hanging. * refactor: extend the JSON validation lint to .json() results no-unvalidated-json-parse now also rejects type assertions on Response, Bun.file and shell .json() results, the same unchecked parse in another form. MCP server tests read /health through a validated readHealth helper and discovery files through parseDiscoveryInfo; the remaining tooling reads its JSON through schemas. * test: validate the RPC request body in the CLI app export test * test: validate CLI JSON output in the tool and app command tests * test: compare the malformed models.dev fallback with the curated list |
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| package.json | ||
| README.md | ||
Release packages and native artifacts
src/workflow.ts owns npm release policy and reuses package-artifacts/package-quality mechanics. src/native/ owns desktop release orchestration. Cross-directory imports within this package use #release/*; sibling modules remain relative.
Canonical release workflow
.github/workflows/build.yml runs on a new stable tag, or through workflow_dispatch with an existing vX.Y.Z tag. A dispatch uses the selected workflow revision but builds the immutable application commit resolved from that tag. The source must be an ancestor of origin/master.
- Plan: resolve the source commit and generate the native matrix from
native/catalog.ts. - Frontend: build packages and the desktop frontend once; prepare and pack npm archives without rebuilding. Archive
dist, desktop icons, and generated menus together and record its SHA-256. - Native: five independent jobs check out that same source, verify and extract the shared inputs, then build through Node's Tauri CLI launcher. A generated config sets only
build.beforeBuildCommandtonull; the checked-in local build hook is unchanged. No native job uploads release assets. - Publish: require the complete matrix with identical source/workflow/run/attempt/frontend identity. Verify file inventories, hashes, every updater signature and the release version it records (the desktop updater sets
requireSignedVersion), and exact source changelog notes. Generate the updater JSON, source/workflow manifest, and checksums; attest and verify the complete output set. Verify npm consumers and publish the original tarballs with npm provenance. Replace every expected draft asset, then download and verify all uploaded bytes.
Workflow tooling is checked out separately in .pipeline and installed from its own frozen lockfile where it runs. Application dependency installation and builds remain owned by the tagged checkout; rebuilding an older tag must not accidentally execute its older release orchestration. Dependency installation is not frontend/package compilation.
The workflow leaves the GitHub Release draft. After successful verification, publish it with the exact tagged changelog body and publish any accompanying security advisory separately. Never move a release tag to repair CI.
Artifact policy and provenance
native/catalog.ts is OpenPencil's required asset/naming policy, not a replacement for Tauri artifact discovery. Collection consumes tauri-action's artifactPaths output and refuses missing, duplicate, unexpected, empty, or escaping files. The macOS .app directory is ignored in favor of its required signed archive. Stable architecture-qualified macOS archive names are preserved.
GitHub's default provenance identifies the workflow execution revision, which can differ from the tagged application source in a manual rebuild. The independently attested release-manifest.json records both commits, the workflow run/attempt, shared frontend digest, target file digests, and npm tarball digests. Do not describe the default workflow provenance alone as proof of the application's checked-out source. npm also emits its own provenance from the same workflow execution; the signed manifest binds its exact tarballs to the application source.
Verify downloaded release files with:
gh attestation verify release-manifest.json --repo open-pencil/open-pencil \
--signer-workflow open-pencil/open-pencil/.github/workflows/build.yml \
--deny-self-hosted-runners
sha256sum --check SHA256SUMS
Each release asset, including SHA256SUMS, also has its own attestation. For a pinned audit, pass the independently obtained workflow commit to --signer-digest; compare the attested manifest's source commit with the immutable tag. Do not treat an unverified manifest as trusted configuration.
Homebrew distribution
The macOS app is distributed through the official openpencil cask: brew install --cask openpencil. This installs the desktop app, not the separately published npm CLI.
Homebrew's BrewTestBot automatically proposes version bumps for this cask (its tooling currently reports a roughly three-hour cadence). Homebrew owns review and merge timing; publishing our GitHub release does not immediately update the cask. Do not add parallel bump-PR automation or push to the archived open-pencil/homebrew-tap repository.
After each release:
- Check the official cask version and search
Homebrew/homebrew-caskfor an existingopenpencilbump PR. - Check that both architecture hashes match the attested release manifest. Follow the bot's PR through upstream review; do not report the cask as updated until it merges.
- If an update is delayed or fails, investigate the upstream bot/PR and follow Homebrew's current contribution policy rather than bypassing autobump restrictions. Direct GitHub downloads and the app updater remain available in the meantime.
The old HOMEBREW_TAP_TOKEN workflow secret is no longer used and can be removed after the obsolete workflow is retired. Revoking the underlying token is a separate credential-owner action if it is shared elsewhere.
Failure and recovery
- There are explicit job/build/startup/command deadlines. Native builds do not silently retry or upload partial release outputs.
- Missing or mixed matrix identities stop publication. Rerun all jobs, not only failed jobs, to obtain a single new run-attempt identity.
- Existing published GitHub releases, moved tags, unexpected draft assets, or already-published npm versions stop preflight. Already-published npm versions require an explicit provenance/recovery review; they are never silently mixed with this run.
- npm publication and GitHub multi-asset uploads are not atomic. A mid-publication failure requires review; the draft is not automatically published or deleted. A failed upload can leave a partial draft, not a successful canonical release.
- Caches improve repeat installs/builds but do not establish provenance. Their scope and writer limits are documented in
.github/actions/setup-bun/README.md.