forked from unom/punktfunk
deef5e43825c1c757e1d0c8a2ab7c3c5a3ec0b18
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1df39d9617 |
fix(ci/sdk): the SDK could not be published at all — bun publish runs prepare
`sdk-v0.1.3` failed at the publish step with `bun2nix: command not found`, exit 127. Nothing was published, so 0.1.3 is still free. `bun publish` runs the `prepare` lifecycle script, and sdk's `prepare` is `bun2nix -o bun.nix` — regenerating the nix dependency file. That tool is a devDependency of the repo, not something the `oven/bun:1` publish container has, and the workflow's own install is `--ignore-scripts`, so nothing put it on PATH either. This was latent, not new. `prepare` gained the bun2nix call on 2026-07-27 ( |
||
|
|
d237646c66 |
fix(host,sdk,kit): library scanners sat in the nav, could not sync local art, and so never got their settings
Three symptoms on .21, two defects. Lutris and Heroic appeared in the console sidebar
they explicitly opt out of; Lutris's settings were unreachable from the Library
screen; and Lutris and Steam logged `sync (startup) failed: HostRequestError`.
**The sidebar is a publish gap.** The console is correct — it keeps
`category: "library"` plugins out of the nav (`uiPlugins`, app-shell.tsx) — but the
host reports no category for them at all. `defineLibraryPlugin` sets it and
`sdk/src/ui.ts` forwards it; what SHIPS does not. `@punktfunk/host` was bumped to
0.1.2 on 2026-07-20 and `category` landed 2026-08-05 without a bump, so the registry's
0.1.2 is the pre-category build and every installed scanner registers without one.
Bumps the SDK to 0.1.3 — **inert until it is published**.
Because the field rides the untyped `pf.request` seam so an older host ignores it
rather than rejecting the registration, dropping it is silent by design. `serveUi` now
reads its own directory entry back and warns once when a requested category did not
land, the same way `defineLibraryPlugin` already warns when a store claim did not take.
That is what turns the next occurrence into a log line instead of a bug report.
**The missing settings and the failed sync are ONE defect: a write/read disagreement
about `file://`.** `local_art_bytes` decodes a `file://` value before testing
containment; `validate_art_paths` handed the raw value to `Path::new`, where
`file:///home/u/c.jpg` is a RELATIVE path whose first component is `file:`. It
canonicalized against the cwd, failed, and read as "outside every art root". So the
host refused every cover the kit's own `fileUrl` helper emits — the documented way for
a plugin to publish local art — while the read path would have served those same files.
That the two symptoms share a cause is not obvious and is why this is one commit: the
Library screen's settings control renders only for `origin: "plugin"`, and a source
becomes `plugin` only once it holds a store CLAIM, which is taken during a successful
reconcile. Lutris failed at entry 0 and Steam at entry 3, so neither ever claimed its
store, both stayed `origin: "builtin"`, and neither got a settings button. Heroic
reconciled (its art is http(s)) and has had its settings all along; rom-manager was
never affected because zero entries meant it never applied.
`art_path_is_servable` now decodes first, so both halves of the confinement judge the
same string. Confinement itself is unchanged: an out-of-root path is still refused in
`file://` clothing, which the test asserts alongside the accept case.
Diagnosing this took the HOST's journal, because both surfaces that should have
explained it lied. `HostRequestError` stringified to its bare tag, so the sync engine's
`${e.cause}` logged `HostRequestError` and discarded the method, the path and the
host's own message; it now renders all three, including an object-shaped cause that
used to print `[object Object]`. And the host logged "payload carries a field this lane
may not set" for BOTH refusals in `check_entry_fields`, so a 400 about an art path read
as an auth problem — it now logs the real reason and the entry title.
Verified on .21 (Linux): 463 host tests pass, clippy clean under `-D warnings`,
`cargo fmt --all --check` clean. The new art test fails without the fix and passes with
it. plugin-kit 71 and SDK 72 tests pass, both typecheck clean, biome clean.
|
||
|
|
b79d90b463 |
fix(deps): close the undici, fast-uri, postcss and brace-expansion advisories
audit.yml's three blocking bun-audit legs (web, sdk, plugin-kit) were all red on
main. Ten findings in sdk and plugin-kit, eight in web; every one of them a
transitive dependency, none reachable by bumping a direct dep.
web already carried the right mechanism — an `overrides` block whose `undici` and
`fast-uri` pins had simply gone stale — so it needed four bumps, not a new idea:
undici 7.28.0 -> ^7.29.0 and fast-uri 3.1.4 -> ^3.1.5 for the reported advisories,
plus postcss ^8.5.10 -> ^8.5.25 and brace-expansion ^5.0.8 -> ^5.0.9 for two more
that were published after the failing run and would have gone red on the next
audit anyway. All four stay inside their current major.
sdk and plugin-kit were harder and the fix deserves an explanation. Their single
finding is undici 8.7.0/8.8.0 pulled in by @effect/platform-node, a devDependency
pinned at 4.0.0-beta.98. That dependency already declares `undici: ^8.7.0`, which
permits the fixed 8.10.0 — the vulnerable version survives purely as a stale
lockfile resolution. Nothing bumps it in place: `bun update` only walks direct
dependencies, `bun install --force` preserves a resolution that still satisfies
its range, and every platform-node release through beta.103 declares the same
`^8.7.0`, so moving the dep changes nothing. Bun rejects the scoped form outright
("Bun currently does not support nested resolutions"), so a flat `overrides` entry
is the only mechanism available, and it necessarily also moves sdk's top-level
undici from 7.x to 8.x.
That is safe here, and was verified rather than assumed. The only source use is
sdk/src/config.ts, which does `new Agent({ connect: { ca } })` behind a dynamic
import and a try/catch with a documented plain-fetch fallback; `Agent` and its
`connect` option are unchanged between undici 7 and 8. sdk typechecks and its 72
tests pass against 8.10.0; plugin-kit typechecks and its 20 tests pass. Both trees
now dedupe to a single undici 8.10.0.
Consumers are deliberately untouched: `overrides` apply only at the root of the
tree that declares them and are not honored when the package is installed as a
dependency, so sdk's published `optionalDependencies: { undici: "^7.0.0" }` is
left alone — a consumer resolves the latest 7.x, which is the fixed 7.29.0. The
override governs this repo's own tree, which is exactly what audit.yml checks.
Worth knowing: sdk's dev tree therefore exercises undici 8 while consumers get 7.
One trap found on the way. Running `bun install` over plugin-kit's existing
lockfile emitted a lockfile with two byte-identical `@punktfunk/host` entries —
its `file:../sdk` dependency crossed with the new override — and bun then refuses
its own output with "Error loading lockfile: InvalidPackageKey". That reads as a
tooling error rather than a finding, so it would have taken the audit gate down
while looking like something else entirely. Regenerating the lockfile from scratch
produces a valid single entry; all three lockfiles are checked for duplicate keys.
Also worth recording, because it nearly shipped: deleting the pinned nested entry
from a lockfile makes `bun audit` report "No vulnerabilities found" while the
vulnerable copy is still installed on disk. bun audit reads the lockfile, not
node_modules. That is a vacuous green, not a fix, and was rejected.
Verified: `bun audit` clean in all three trees; web builds and typechecks (its
typecheck needs the build first, which generates routeTree.gen); sdk 72/72 and
plugin-kit 20/20 tests pass.
|
||
|
|
1db8f7631b |
fix(nix): move the bun packages to bun2nix — no more hand-bumped deps hash
`nix build .#punktfunk-web` has been broken since |
||
|
|
c15c80718a |
chore(sdk): bump @punktfunk/host to 0.1.2
Manual publish (no sdk-v tag — the version was published directly so the plugin-kit and rewritten rom-manager can consume pluginStateDir/pluginIngestDir from the registry; the previously published 0.1.1 predates the security-tiers merge and lacks those exports). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
94533bb071 |
feat(scripting): discover scoped @punktfunk/plugin-* packages
The runner only found unscoped `punktfunk-plugin-*` packages. An unscoped package can't be split across registries, which forced plugins to bundle their own copy of @punktfunk/host + effect (the plugin's Gitea registry has no `effect`; `--registry <gitea>` 404s it). Discovering the scoped `@punktfunk/plugin-*` convention lets a plugin resolve cleanly from one scope map and depend on @punktfunk/host + effect as SHARED (hoisted) deps instead of bundling — no per-plugin duplication. Additive: unscoped names still work. Bumps @punktfunk/host to 0.1.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
691c064a37 |
build(sdk): publish @punktfunk/host to the Gitea npm registry
- package.json: drop private; point main/types/exports/bin at a tsc-built dist/; add publishConfig (unom/npm registry), files, repo metadata, and the MIT OR Apache-2.0 license; effect becomes a peerDependency (shared instance). - tsconfig.build.json: emit dist/ JS + .d.ts (bun shebang preserved on the bin). - .npmrc: map the @punktfunk scope to the registry (no token committed). - sdk-publish.yml: publish on sdk-v* tags or manual dispatch, reusing the REGISTRY_TOKEN secret; typecheck/test/build/tag-matches-version gate. - README: Install section for consumers. Verified: build green, frozen lockfile stable, bun publish --dry-run packs @punktfunk/host@0.1.0 (dist + README only) to the unom registry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f012ebbcba |
feat(sdk): Effect v4 + @effect/openapi-generator; typed pf.api & example ladder
Drop Orval for the first-party @effect/openapi-generator (OpenAPI 3.1 -> Effect Schema + a typed HttpClient client) and bump effect 3.19 -> 4.0.0-beta.98. Port the hand-written surfaces to the v4 API (Result over Either, Context.Service, Codec, Literals/Union arrays, Stream/Schedule/ Effect renames). Transport (CA-pinning fetch) and the reconnecting SSE source are kept intact. Make the SDK approachable for non-Effect users: - Add pf.api.* on the Promise facade: the generated client surfaced as typed, Promise-native methods (await pf.api.listPairedClients()), so REST calls are autocompleted and checked instead of stringly-typed pf.request(method, path, body) + `as` casts. Zero-drift veneer over make(httpClient), backed by the same pinning fetch. pf.request stays as the untyped escape hatch. - Re-tier examples into a 1-4 complexity ladder, rewritten onto pf.api.* (the typed payloads caught a wrong `launch` shape in provider-sync); the Effect example is labelled advanced. Add examples/ to tsconfig so they are typechecked (stops rot). typecheck + 19 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e06ab59652 |
feat(sdk): punktfunk-scripting — the managed script/plugin runner (M5)
The optional supervision layer (RFC §8): one service runs everything in <config_dir>/scripts/ plus installed punktfunk-plugin-* packages (<config_dir>/plugins/node_modules/), as Effect fibers. - Plugins (a definePlugin default export, either main shape) are SUPERVISED: a failure restarts them with capped exponential backoff (jittered, 1s→60s); a clean return completes them. The Effect shape runs under the PunktfunkHost layer; the async-fn shape gets a facade client whose close is scope-guaranteed. - Bare scripts are one-shot: importing them is the run, no restart (export a plugin to be supervised). - Shutdown is STRUCTURAL: SIGINT/SIGTERM interrupt the whole fiber tree, so Effect plugins' scoped finalizers run and clients close before exit — the systemctl-stop story, and the reason the Effect plugin shape exists at all. - The sshd rule applies to unit files (world-writable → refused loudly); cache-busted imports make restarts real; --list for inventory. 6 new bun tests (17 total green): discovery + refusal, both plugin shapes against a mock host, crash→restart with backoff, one-shot semantics, and finalizer-on-interrupt. Live-verified against a real host: a supervised watcher plugin received library.changed through the pinned tunnel, and SIGTERM shut the tree down structurally (exit 0). Deferred to the packaging follow-up (release.yml is in flight in a parallel session): the vendored-Bun deb/rpm/iss packages and the host-log-ring tee (needs a host ingest endpoint); console page rides the other console surfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
87114ab186 |
feat(sdk): @punktfunk/host — the Effect TypeScript SDK (M3)
New top-level sdk/ package (RFC §7): a typed management-API client plus the lifecycle event stream, built on Effect, two surfaces over one core: - @punktfunk/host — the Promise facade front door: connect() resolves URL/token/TLS pin from the host's own files (zero config on the box), fails fast on bad credentials, pf.events.on() with typed callbacks (exact kinds, domain.* prefixes, "*", "dropped", "unknown"), pf.request() for the REST surface. Effect never required. - @punktfunk/host/effect — the PunktfunkHost service + PunktfunkHostLive layer, Stream-based events()/eventsRaw(), typed errors (AuthError | ApiError | TransportError | VersionSkew — a 2xx that fails its schema is a typed skew, not undefined later), and every wire shape as an effect/Schema: REST generated via orval client:'effect' from api/openapi.json (S3 spike: works well; the text/event-stream payload is out of its reach), events hand-mirrored from the host's snapshot-tested wire format as a kind-discriminated union. One reconnecting SSE core under both surfaces: spec-shaped parser, exponential+jittered backoff (capped, resets after a healthy connection), Last-Event-ID resume, 401 terminal. Default is LIVE tail only — a fresh notify script must not re-fire on the host's replayed ring (since: 0 opts into full replay). TLS: the pin trusts exactly the host's self-signed identity cert (chain-verified; hostname check waived — the cert is deliberately CN-only for fingerprint pinning). Bun via fetch tls, Node via an undici dispatcher (optionalDependency). definePlugin() accepts both main shapes (async fn | Effect requiring PunktfunkHost). Examples in both styles; README carries the compat contract + systemd/Task Scheduler templates. 11 bun tests green (wire decode against the Rust snapshot strings, SSE parser/reconnect/Last-Event-ID/401, both surfaces vs a mock host). Live-verified against a real host on Bun AND Node through the pinned loopback hop: connect → REST mutate → live event received → resume cursor advanced; a wrong CA is rejected. npm publish + CI wiring deferred (npm org = RFC open question 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |