feat(plugins/lifecycle): state machine — closes #45#339
Merged
Conversation
2 tasks
tayebmokni
added a commit
that referenced
this pull request
May 17, 2026
## Summary - `go list -m all` walks transitive deps; vetting downloaded module source from `$GOPATH/pkg/mod` fails on packages that reference deps we don't have (testcontainers → docker → containerd/v2; some testutil paths → dgryski/trifles) - Replace with iteration over `go.work` `use` directives — the canonical first-party module list - Same fix applied to both `lint-go: go vet` and `test-go: go test -race` This is the root cause of lint-go failures on the entire Wave C: PRs #328, #329, #334, #335, #336, #338, #339. ## Test plan - [ ] CI passes on this PR - [ ] After merge, rebase Wave C PRs and verify they go green 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com> Co-authored-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
6f18602 to
fc9864d
Compare
2 tasks
tayebmokni
added a commit
that referenced
this pull request
May 17, 2026
## Summary - Workspace is `go 1.25.0`; golangci-lint v1.61.0 was built with go1.23 and refuses to lint - Bump to v2.12.2 (built with go1.25) - No `.golangci.yml` in repo, so the v1→v2 config-format change has no migration cost After this lands, rebase Wave C PRs to pick it up. Should unblock #328, #329, #334, #335, #336, #338, #339, #341. ## Test plan - [ ] CI passes here - [ ] Wave C PRs pass after rebase 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com> Co-authored-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
fc9864d to
6131729
Compare
…machine — closes #45 Signed-off-by: Claude (for tayebmokni) <tayeb.mokni@gmail.com>
6131729 to
c62950e
Compare
11 tasks
tayebmokni
added a commit
that referenced
this pull request
May 18, 2026
…loses #245 (#371) ## Summary Closes #245. Adds the `gonext plugin dev <project-dir>` subcommand for plugin authors: auto-detect the toolchain, build to WASM, upload to a running dev host, watch for changes, and hot-reload. Lives in `cli/gonext/cmd/plugin/`. Foundation merged so the plugin platform now has: wazero runtime (#350), manifest validator (#345), capability registry (#344), instance pool (#357), resource limits (#356), hook ABI (#364), lifecycle (#339). This PR is the productive author-side dev loop on top of all that. ### Flags ``` gonext plugin dev [flags] <project-dir> --host URL of the running gonext dev host (default http://localhost:8080) --watch Watch the project tree and hot-reload on change (default true) --build-only Build only; skip upload and watch --lang auto | go | tinygo | rust (default auto) ``` ### Architecture The orchestrator (`runDevLoop`) runs four phases — detect → build → upload → watch — and every I/O dependency funnels through an injectable seam: | Seam | Production | Test | |---|---|---| | `CommandRunner` | `execRunner` (exec.CommandContext) | `fakeRunner` recording argv + optional Hook to plant artifacts | | `Uploader` | `httpUploader` (multipart POST) | `stubUploader` counter / `httptest.Server` | | `Watcher` (factory) | `fsnotifyWatcher` (recursive Add) | `fakeWatcher` (buffered chans) | | `Now func()` | `time.Now` | frozen clock | That lets the watch+rebuild loop be unit-tested end-to-end without forking, real filesystem races, or network round-trips. 64 test cases across six files. ### Per-language build commands | Language | Detection | Command | Output | |---|---|---|---| | TinyGo | `go.mod` present | `tinygo build -o build/plugin.wasm -target=wasi .` | `build/plugin.wasm` | | Rust | `Cargo.toml` present | `cargo build --target wasm32-wasi --release` | Copied from `target/wasm32-wasi/release/*.wasm` to `build/plugin.wasm` | Both markers present produces an ambiguity error rather than a silent pick. The Rust path errors if cargo produces more than one `.wasm` (asks the author to set `[lib].name`). ### Watch loop - `fsnotify` recursively registers the project tree at startup, skipping `build/`, `target/`, `node_modules/`, `.git/`, `.idea/`, `.vscode/`, `vendor/`, `dist/`, `__pycache__/`. - Generic `debounce[T any]` with a 200ms trailing-edge window collapses bursty inotify events (single save → single rebuild). Tested with 20–30ms windows and synthetic event sources. - Editor tempfiles (`*~`, `.#*`) and our own `build/` writes are filtered so the loop doesn't feed itself. - First-pass build/upload errors propagate out (non-zero exit). Errors during watch are printed but the loop keeps running — the operator's next save is probably the fix. - SIGINT / SIGTERM → graceful exit 0. ### Capability diff After each successful build the manifest's `capabilities` list is compared against the previous snapshot: ``` [19:11:29] detected language: tinygo [19:11:29] build: tinygo [19:11:29] upload: http://localhost:8080 capabilities: = http.fetch = db.read [19:11:30] uploaded successfully [19:11:30] watching /path/to/plugin (Ctrl-C to stop) [19:11:42] change detected — rebuilding capabilities changed: + media.write - db.read ``` This is what gives the operator a clear before/after view between hot reloads — the capability set is what the host is being asked to grant. ## TODO — host-side endpoint follow-up This CLI uploads to `POST ${host}/_/plugins/dev/install` but the host-side endpoint is **not in this PR**. The contract this CLI assumes: - Path: `/_/plugins/dev/install` - Method: `POST` - Content-Type: `multipart/form-data` - Parts: - `manifest` — file part, `application/json`, the `manifest.json` bytes - `wasm` — file part, `application/wasm`, the compiled module - Success: any 2xx (body ignored) - Failure: any non-2xx; body's first 1KiB is included verbatim in the CLI's error message The natural host-side implementation calls the existing lifecycle Manager's `Install(ctx, bundle io.Reader)` — that function already validates the manifest and handles slug/version/abi extraction. The dev install endpoint would also need to bypass signature verification (dev-only) and idempotently re-activate on every upload. Followup issue suggested: \"feat(server): host-side dev plugin install endpoint\". ## Test plan - [x] `cd cli/gonext && go vet ./...` — clean - [x] `cd cli/gonext && go test -race -count=1 ./cmd/plugin/...` — 64 cases pass under `-race` - [x] `cd cli/gonext && go test -race -count=1 ./...` — entire CLI module green - [x] `cd cli/gonext && go build ./...` — builds - [x] Language detector: tinygo via `go.mod`, rust via `Cargo.toml`, ambiguous error when both present, missing-marker error, dir-not-file rejected - [x] Build orchestrator: TinyGo argv = `[build -o <abs>/build/plugin.wasm -target=wasi .]`, Rust artifact normalisation, multiple `.wasm` error path, runner failure propagates - [x] Multipart upload: httptest server roundtrip verifies path, Content-Type, both parts, byte fidelity; host trailing-slash + subpath join correctly; 4xx body preview surfaces; missing wasm errors; relative host rejected - [x] Debounce: 5-event burst collapses to 1 emission; two bursts 60ms apart fire twice; ctx-cancel closes out-channel; input-close closes out-channel - [x] Capability diff: first build prints `=` list, no-change emits nothing, add/remove deltas use `+`/`-` - [x] Dispatcher: `--build-only` skips upload + watch, watch+rebuild on event, watch keeps running through build error and recovers on next event, usage/help/missing-arg/extra-arg/not-a-directory exit codes - [ ] Manual smoke against a running dev host — gated on the host-side endpoint follow-up 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com> Co-authored-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
6 tasks
tayebmokni
added a commit
that referenced
this pull request
May 19, 2026
…406) ## Summary Wires `apps/web/` as a real Next.js App Router app that turns a merged block tree + active theme + resolved template into rendered HTML pages. All foundations (blocks-core #352/#378, theme parser #319, template hierarchy #328, theme seeding #381, gn-hello #354, gn-pro #358, block render walker #339) already landed — this PR ties them together so a visitor on the site sees something. - **Catch-all route** (`src/app/[...slug]/page.tsx`): fetch post by slug → resolve template → walk blocks → wrap in theme parts. Falls through to the 404 template path when no post matches. - **Homepage** (`src/app/page.tsx`): renders the latest-posts archive. - **Block walker** (`src/lib/blocks.ts`): mirrors the Go walker. Seeds the 16 core handlers from a new `@gonext/blocks-core/server` sub-entry (see below); plugins extend via `registerBlock(name, fn)`. - **Typed API client** (`src/lib/api.ts`): four endpoints with inline fallbacks + documented TODOs so the renderer is testable end-to-end while the Go endpoints are wired. - **Theme helper** (`src/lib/theme.ts`): `defaultActiveTheme()` matches gn-hello so the site still paints when the API is offline. - **Renderer** (`src/lib/render.ts`): centralises cache policy: - logged-out singular/archive: `public, s-maxage=300, stale-while-revalidate=86400` - logged-out 404: `public, s-maxage=60, stale-while-revalidate=300` - logged-in (any session cookie present): `private, no-store` Also adds **`@gonext/blocks-core/server`** — a new SSR-only sub-entry that exposes just the pure `serverRender` / `save` hints (no React, no editor surface). The main barrel re-exports `Edit` components, each of which transitively imports React hooks without `'use client'`; pulling those into a server component fails the Next.js build. The new entry sidesteps that for any SSR consumer. ## Endpoints assumed (documented TODOs in `src/lib/api.ts`) - `GET /api/v1/posts/by-slug/{slug}` - `GET /api/v1/themes/active` - `GET /api/v1/themes/active/template?type=...&postType=...&...` - `GET /api/v1/posts?status=published&postType=...&limit=...` Each call site treats network errors / 404 as "endpoint unavailable" and falls back to a documented `<template>.fallback` basename, so e2e tests can distinguish API path from fallback path without parsing headers. ## Test plan - [x] `pnpm --filter @gonext/web test` — 44 tests pass - [x] `pnpm --filter @gonext/web typecheck` — clean - [x] `pnpm --filter @gonext/web lint` — no warnings - [x] `pnpm --filter @gonext/web build` — succeeds, standalone bundle emitted - [x] `pnpm --filter @gonext/blocks-core test` — 163 tests still pass (new `./server` entry doesn't break anything) - [ ] Wire the four documented endpoints on the Go side and verify the fallback paths drop out of e2e snapshots 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com> Co-authored-by: Mohamed Tayeb Mokni <tayeb.mokni@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the plugin lifecycle state machine — install / activate / deactivate / uninstall with persistence — per
docs/02-plugin-system.md§3.This is the state-machine + persistence half of the plugin system. The WASM runtime (issue #6) and bundle verification (issue #44) are out of scope; the
Managerwires them throughRuntimeandMigratorseams that default toNoopRuntime/NoopMigrator.What landed in
packages/go/plugins/lifecycle/Stateenum:Installed,Active,Inactive,PendingUninstall,Errored.Pluginstruct: slug / version / abi_version / manifest (raw JSON) / state / capabilities / last_error / error_at / installed_at / activated_at / row_version / updated_at.ManagerwithInstall,Activate,Deactivate,Uninstall(removeData),Reset,Get,List.Storageinterface with two implementations:MemoryStoragefor unit tests + dev.PostgresStorageagainst thepluginstable (CREATE TABLE migration deferred; schema documented indoc.go).Storage.UpdateState, which performs a conditionalUPDATE … WHERE slug = $? AND state = $expected. Concurrent callers race at the database (or, forMemoryStorage, at the mutex) — exactly one wins, the rest receiveErrInvalidTransition.plugin.installed,plugin.activated,plugin.deactivated,plugin.uninstalled,plugin.errored,plugin.reset. Failed audit emission is logged (never blocks the transition).LastError+ErrorAton any failed transition.Resetis the only path back toInactive.Test plan
MemoryStorage.ErrInvalidTransition(e.g.ActivatefromPendingUninstall,UninstallfromActive,ResetfromActive).Activaterace: exactly one wins, the rest returnErrInvalidTransition. Clean under-race.Erroredrecovery: install → fail-activate (runtime error injected) →Reset→Activate→Active. Audit chain intact.audit.MemoryStore.PostgresStorage: insert / get / list / update-state / delete paths covered with a fakePgxQuerier. Unique-violation translation tested. CAS lost-race producesErrInvalidTransition.go build ./plugins/lifecycle/...clean.go vet ./plugins/lifecycle/...clean.go test -race -count=1 -cover ./plugins/lifecycle/...→ 92.9 % coverage (83 tests, all passing).🤖 Generated with Claude Code