Skip to content

feat: make Spock the unified framework host - #15

Merged
softmarshmallow merged 15 commits into
mainfrom
feature/spock-framework
Jul 15, 2026
Merged

feat: make Spock the unified framework host#15
softmarshmallow merged 15 commits into
mainfrom
feature/spock-framework

Conversation

@softmarshmallow

@softmarshmallow softmarshmallow commented Jul 14, 2026

Copy link
Copy Markdown
Member

Summary

  • make the installed spock command project-aware with required spock.toml, one required backend, and an optional Uhura client
  • add spock new, spock init, project-wide check, fixed start, and watched dev while preserving the existing file-oriented language commands
  • add spock-project and spock-host boundaries for manifest/discovery/scaffolding and combined generation/listener/lifecycle ownership
  • serve Spock Studio, Uhura Editor, Uhura Play, framework status/environment, and authority protocols from one process, port, and origin
  • ship the reusable Uhura web/Wasm sidecar and four native launchers in the npm package

Pins the merged Uhura main commit from gridaco/uhura#9 (baa70ce) through the existing submodule boundary.

Doctrine and reload contract

Operational composition does not merge language ownership:

  • Spock owns durable product truth, policy, and guarded mutations.
  • Uhura owns presentation, experience transitions, and non-authoritative UI-session state.
  • spock.toml composes roots and lifecycle; it does not absorb uhura.toml or reinterpret either language.
  • No fact becomes authoritative in both systems.

spock dev is deliberately client-live/backend-pinned. Valid client saves publish last-known-good generations. Backend source, referenced seed assets, and topology changes are watched and reported as restart_required, but never reopen, reseed, migrate, or replace the active database. RFD 0023 retains the unsolved development-world/migration decision, with exactly one TODO(RFD-0023) marker in Rust.

Release-review hardening

  • drain accepted requests and close SSE streams before releasing backend lifecycle or the named-database lock
  • re-resolve watched manifest/backend/client paths every coherent frame; safe retargets publish, escapes reject without a backend swap
  • isolate named-state locks from mutable database paths and pin destructive bootstrap to the exact locked directory entry
  • make project creation/adoption capability-pinned, alias-safe, and rollback-conservative across Unix and Windows
  • bound ordinary Uhura requests separately from SSE, share stream admission across project, Editor, and Play, and release capacity on disconnect
  • make npm dry runs install and exercise the exact guarded tarball; real publishing consumes that same artifact
  • align README, npm guidance, Studio, distribution, framework, and reload-study documents with the shipped topology

Verification

  • root CI: workspace formatting, warnings-denied clippy, tests, canonical project checks, and independent Uhura clean-checkout gates
  • Uhura CI: formatting, warnings-denied clippy, Rust tests, browser/provider checks, and canonical project validation
  • local release-boundary audits: descriptor-exhaustion rollback, Windows cross-compilation, large-body HEAD/Content-Length, SSE admission/disconnect reuse, and shared project/Editor/Play stream limits
  • final spock@0.5.0 npm dry run: all four native builds, guarded 21-file / 18.4 MB tarball, dry publish, and installed framework-route/asset verification on macOS, Linux, and Windows
  • CodeRabbit incremental review on 704dc53 generated no actionable comments; all 13 original review threads are resolved

Explicitly deferred

  • backend hot reload, migration, rebase, and state-preserving activation
  • Play runtime-state HMR
  • multiple backends/clients and production deployment semantics

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Spock 0.5.0 adds project manifests, scaffolding and adoption, a combined framework host with client/backend lifecycle handling, immutable runtime generations, authenticated Uhura assets, expanded CLI workflows, and guarded cross-platform npm packaging.

Changes

Framework implementation

Layer / File(s) Summary
Project topology and scaffolding
Cargo.toml, crates/spock-project/*, crates/spock-project/templates/*
Adds strict manifests, portable path validation, discovery, layout loading, scaffold/adoption planning, starter client templates, and integration tests.
Race-safe project writes
crates/spock-cli/src/write_plan.rs
Applies planned writes with pinned roots, deterministic ordering, platform-specific identity handling, journaling, and rollback reporting.
Backend runtime generations
crates/spock-runtime/src/engine.rs, crates/spock-runtime/src/generation.rs, crates/spock-runtime/src/http.rs
Adds captured seed-asset loading, immutable backend generations, fingerprints, lifecycle guards, authority-only routing, and empty-contract GraphQL handling.
Host state and assets
crates/spock-host/src/{assets,backend_capture,client,events,generation,named_state,project}.rs
Adds authenticated Uhura sidecar loading, coherent backend observation, client publication state, SSE invalidation, project status coordination, diagnostics, and named database locking.
Combined HTTP and development server
crates/spock-host/src/{routing,http,server}.rs
Adds route ownership, framework status/environment/health/event endpoints, combined authority/client fallback routing, graceful shutdown, and client-live/backend-pinned development observation.
CLI workflows
crates/spock-cli/src/{lib,main,project_commands}.rs, crates/spock-cli/tests/cli.rs
Adds reusable checking, artifact generation, standalone hosting, new, init, start, and dev workflows with diagnostics, signal handling, and lifecycle tests.

Distribution and release

Layer / File(s) Summary
Guarded npm distribution
.github/workflows/{ci,npm}.yml, npm/{bin,scripts,package.json}, crates/spock-host/src/assets.rs
Pins build tooling, builds Uhura web/Wasm assets, assembles and verifies a manifest-backed sidecar, packages platform binaries, forwards process signals, and verifies installed routes and assets.
Release documentation
README.md, npm/README.md, CHANGELOG.md, docs/rfd/*, npm/{LICENSE,THIRD_PARTY_NOTICES.md}
Documents the 0.5.0 framework model, project lifecycle, development reload policy, Studio integration, sidecar distribution, and release constraints.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SpockCLI
  participant SpockHost
  participant BackendGeneration
  participant UhuraClient
  participant Browser
  User->>SpockCLI: spock dev
  SpockCLI->>SpockHost: serve_project
  SpockHost->>BackendGeneration: capture and activate backend
  SpockHost->>UhuraClient: load, validate, and publish client
  SpockHost->>Browser: serve combined HTTP routes
  Browser->>SpockHost: request status, authority, or client route
  SpockHost-->>Browser: response or SSE invalidation event
Loading

Possibly related PRs

  • gridaco/spock#6: Updates the Uhura submodule and repository topology used by the asset build and packaging flow.
  • gridaco/spock#9: Also modifies the runtime HTTP routing layer and its browser-facing CORS behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: Spock becomes the unified framework host.
Description check ✅ Passed The description matches the changeset and covers the project-aware CLI, host boundaries, serving, packaging, and reload behavior.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/spock-framework

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@softmarshmallow

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (2)
crates/spock-host/src/project.rs (1)

427-451: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Rename this status field or change the payload. client_source_fingerprint is a source snapshot identity, but ActiveClientStatus::artifact_fingerprint exposes it under an artifact name. Either publish a real served-artifact fingerprint here or rename the protocol field to source_fingerprint before consumers rely on it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/spock-host/src/project.rs` around lines 427 - 451, Align the status
payload with the value produced by client_source_fingerprint: either replace it
with the fingerprint of the served artifact, or rename
ActiveClientStatus::artifact_fingerprint and its protocol consumers to
source_fingerprint. Ensure the published field name and semantics consistently
identify the client source snapshot.
crates/spock-project/src/layout.rs (1)

126-178: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider deduplicating expect_directory/expect_file.

Both helpers are identical except for the metadata predicate and label text. A single generic helper parameterized by an is_dir/is_file check would remove the duplication.

♻️ Example consolidation
-fn expect_directory(path: &Path, label: &str, diagnostics: &mut Diagnostics) {
-    match fs::metadata(path) {
-        Ok(metadata) if metadata.is_dir() => {}
-        Ok(_) => diagnostics.push(
-            Diagnostic::new(
-                DiagnosticCode::WrongEntryKind,
-                format!("{label} is not a directory"),
-            )
-            .at_path(path),
-        ),
-        ...
-    }
-}
-
-fn expect_file(path: &Path, label: &str, diagnostics: &mut Diagnostics) {
-    match fs::metadata(path) {
-        Ok(metadata) if metadata.is_file() => {}
-        Ok(_) => diagnostics.push(
-            Diagnostic::new(
-                DiagnosticCode::WrongEntryKind,
-                format!("{label} is not a regular file"),
-            )
-            .at_path(path),
-        ),
-        ...
-    }
-}
+fn expect_entry_kind(
+    path: &Path,
+    label: &str,
+    expected_kind: &str,
+    matches: impl Fn(&fs::Metadata) -> bool,
+    diagnostics: &mut Diagnostics,
+) {
+    match fs::metadata(path) {
+        Ok(metadata) if matches(&metadata) => {}
+        Ok(_) => diagnostics.push(
+            Diagnostic::new(
+                DiagnosticCode::WrongEntryKind,
+                format!("{label} is not {expected_kind}"),
+            )
+            .at_path(path),
+        ),
+        Err(error) if error.kind() == std::io::ErrorKind::NotFound => diagnostics.push(
+            Diagnostic::new(DiagnosticCode::MissingInput, format!("{label} does not exist"))
+                .at_path(path),
+        ),
+        Err(error) => diagnostics.push(
+            Diagnostic::new(DiagnosticCode::Io, format!("could not inspect {label}: {error}"))
+                .at_path(path),
+        ),
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/spock-project/src/layout.rs` around lines 126 - 178, Deduplicate the
metadata validation logic shared by expect_directory and expect_file by
introducing one helper parameterized with the expected entry-kind predicate and
message. Preserve each function’s existing DiagnosticCode, labels, path
attachment, and directory-versus-regular-file wording while having both wrappers
reuse the common implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/spock-cli/src/write_plan.rs`:
- Around line 438-443: The public apply_write_plan API must not invoke the
race-safe inner flow without a prepared filesystem target. Update
apply_write_plan to require and pass a retained prepared lease/target into
apply_write_plan_inner, or remove/rename this pathname-only entry point so it no
longer presents itself as race-safe; preserve the existing ApplySummary and
ApplyError behavior.

In `@crates/spock-host/src/assets.rs`:
- Around line 280-298: The asset validation flow around the manifest
verification loop and its subsequent serving logic must authenticate the sidecar
manifest against a trusted executable-embedded identity. Add a trusted manifest
digest, signature, or expected build identity to the binary, verify the loaded
manifest before comparing file entries, and reject mismatches before assets are
served; retain the existing file path, size, and SHA-256 checks.

In `@crates/spock-host/src/backend_capture.rs`:
- Around line 482-504: Update the backend source-loading flow around
canonical_source and the corresponding secondary input path to avoid reopening
validated paths with fs::read. Open each input through a no-follow,
root-constrained handle, validate the opened object’s identity and regular-file
status against the configured root, then read bytes from that same handle so
renames or symlink swaps cannot bypass PathEscape.

In `@crates/spock-host/src/events.rs`:
- Around line 72-103: Introduce shared session-wide admission control for all
event streams: update ProjectEventHub::subscribe and ProjectEventStream::drop in
crates/spock-host/src/events.rs (lines 72-103) to acquire a permit before
registering subscribers and release it on drop; update the project-event
subscription handling in crates/spock-host/src/http.rs (lines 145-150) to reject
requests when permits are exhausted; update the proxied Uhura stream flow in
crates/spock-host/src/http.rs (lines 303-365) to acquire the same limit and
release the permit on every termination path.

In `@crates/spock-host/src/generation.rs`:
- Around line 363-379: Update reject_client to distinguish initial rejection
from rejection while retaining an active client: set client_freshness to the
cold-rejected state when active_client is absent, and preserve RejectedLastGood
when it is present. Keep the existing editor_freshness behavior aligned with the
same active_client check.

In `@crates/spock-host/src/http.rs`:
- Line 185: Update the merged host router construction to remove
CorsLayer::permissive() or replace it with an explicit trusted-origin allowlist.
Ensure the authority and framework routes are not accessible from arbitrary
browser origins while preserving the existing router merge behavior.

In `@crates/spock-host/src/named_state.rs`:
- Around line 171-185: Update resolved_database_entry and the named-state
ownership flow to prevent hard-linked SQLite files from receiving independent
ownership based only on their final names. After resolving the database entry,
inspect the existing file’s identity and reject files with multiple hard links,
or use that identity in the lock digest and WAL/SHM coordination so all aliases
share ownership. Preserve the current path-based behavior for non-hard-linked
databases.

In `@crates/spock-host/src/project.rs`:
- Around line 388-415: Extend the final stability check after client preparation
to re-capture the client input fingerprint and compare it with the fingerprint
used for the immutable snapshot. Use the existing client-layout/capture symbols
around capture_stable_client and return HostError::UnstableProject when the
client inputs changed, while preserving the topology and backend checks.

In `@crates/spock-host/src/server.rs`:
- Around line 374-435: Update the publication failure branch after
begin_client_attempt to transition the client out of Building by recording the
attempt as rejected/failed and publishing the corresponding session event.
Preserve the last-good client state, or roll back to it when installation
already changed the active client, before emitting ObserverError so subscribers
receive the final invalidation.
- Around line 180-184: Update the shutdown sequence around the observer join
handling so its result is captured instead of propagated immediately; always
call lifecycle.shutdown().await before propagating an observer failure, then
propagate the captured join result while preserving server_result? handling.

In `@crates/spock-project/src/plan.rs`:
- Around line 568-585: Update finish_plan’s write-destination validation to
reject file/descendant collisions such as `foo` and `foo/bar`, not only
duplicate normalized paths. While iterating the sorted writes and using the
existing portable path normalization, detect when either the current path is an
ancestor of an already-seen path or an already-seen path is an ancestor of the
current path, emit a PlanConflict diagnostic, and prevent the plan from passing
preflight.
- Around line 158-164: Update the inventory entry classification around
file_type.is_symlink() and file_type.is_dir() so FIFOs, sockets, devices, and
other non-regular filesystem entries are rejected or represented as explicitly
unsupported rather than classified as InventoryEntryKind::File. Preserve
symlink, directory, and regular-file handling, and ensure unsupported entries
cannot become backend candidates.

In `@npm/scripts/sidecar.mjs`:
- Around line 137-140: Reorder the validation in regularFiles so
rootStat.isSymbolicLink() is checked before rootStat.isDirectory(). Preserve the
existing symlink-specific failure message, while using the missing-directory
error only for non-symlink paths that are not directories.

---

Nitpick comments:
In `@crates/spock-host/src/project.rs`:
- Around line 427-451: Align the status payload with the value produced by
client_source_fingerprint: either replace it with the fingerprint of the served
artifact, or rename ActiveClientStatus::artifact_fingerprint and its protocol
consumers to source_fingerprint. Ensure the published field name and semantics
consistently identify the client source snapshot.

In `@crates/spock-project/src/layout.rs`:
- Around line 126-178: Deduplicate the metadata validation logic shared by
expect_directory and expect_file by introducing one helper parameterized with
the expected entry-kind predicate and message. Preserve each function’s existing
DiagnosticCode, labels, path attachment, and directory-versus-regular-file
wording while having both wrappers reuse the common implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2fa7a32b-2a13-4827-9eac-388fc0e46659

📥 Commits

Reviewing files that changed from the base of the PR and between 4c964bf and 5f81328.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (61)
  • .github/workflows/ci.yml
  • .github/workflows/npm.yml
  • .gitignore
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • crates/spock-cli/Cargo.toml
  • crates/spock-cli/src/lib.rs
  • crates/spock-cli/src/main.rs
  • crates/spock-cli/src/project_commands.rs
  • crates/spock-cli/src/write_plan.rs
  • crates/spock-cli/tests/cli.rs
  • crates/spock-host/.gitignore
  • crates/spock-host/Cargo.toml
  • crates/spock-host/src/assets.rs
  • crates/spock-host/src/backend_capture.rs
  • crates/spock-host/src/client.rs
  • crates/spock-host/src/events.rs
  • crates/spock-host/src/generation.rs
  • crates/spock-host/src/http.rs
  • crates/spock-host/src/lib.rs
  • crates/spock-host/src/named_state.rs
  • crates/spock-host/src/project.rs
  • crates/spock-host/src/routing.rs
  • crates/spock-host/src/server.rs
  • crates/spock-project/.gitignore
  • crates/spock-project/Cargo.toml
  • crates/spock-project/src/diagnostic.rs
  • crates/spock-project/src/discovery.rs
  • crates/spock-project/src/layout.rs
  • crates/spock-project/src/lib.rs
  • crates/spock-project/src/manifest.rs
  • crates/spock-project/src/path.rs
  • crates/spock-project/src/plan.rs
  • crates/spock-project/src/starter.rs
  • crates/spock-project/templates/minimal-client/app/home/page.examples.uhura
  • crates/spock-project/templates/minimal-client/app/home/page.uhura
  • crates/spock-project/templates/minimal-client/catalog/base.toml
  • crates/spock-project/templates/minimal-client/fixtures/empty.toml
  • crates/spock-project/templates/minimal-client/fixtures/scripts/empty.toml
  • crates/spock-project/templates/minimal-client/uhura.toml
  • crates/spock-project/tests/project_flow.rs
  • crates/spock-runtime/src/engine.rs
  • crates/spock-runtime/src/generation.rs
  • crates/spock-runtime/src/http.rs
  • crates/spock-runtime/src/lib.rs
  • crates/spock-runtime/studio/README.md
  • crates/spock-runtime/tests/generation.rs
  • docs/rfd/0015-studio.md
  • docs/rfd/0020-distribution.md
  • docs/rfd/0022-spock-framework.md
  • docs/rfd/0023-development-state-reload.md
  • docs/rfd/README.md
  • npm/LICENSE
  • npm/README.md
  • npm/THIRD_PARTY_NOTICES.md
  • npm/bin/spock.js
  • npm/package.json
  • npm/scripts/sidecar.mjs
  • rust-toolchain.toml
  • uhura

Comment thread crates/spock-cli/src/write_plan.rs Outdated
Comment thread crates/spock-host/src/assets.rs
Comment thread crates/spock-host/src/backend_capture.rs Outdated
Comment thread crates/spock-host/src/events.rs Outdated
Comment thread crates/spock-host/src/generation.rs
Comment thread crates/spock-host/src/server.rs Outdated
Comment thread crates/spock-host/src/server.rs Outdated
Comment thread crates/spock-project/src/plan.rs
Comment thread crates/spock-project/src/plan.rs
Comment thread npm/scripts/sidecar.mjs Outdated
@softmarshmallow
softmarshmallow merged commit 8697ecd into main Jul 15, 2026
12 checks passed
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