Skip to content

feat(container): implement perry/container and perry/container-compose#159

Draft
yumin-chen wants to merge 1 commit intoPerryTS:mainfrom
yumin-chen:feat/container-compose
Draft

feat(container): implement perry/container and perry/container-compose#159
yumin-chen wants to merge 1 commit intoPerryTS:mainfrom
yumin-chen:feat/container-compose

Conversation

@yumin-chen
Copy link
Copy Markdown

@yumin-chen yumin-chen commented Apr 23, 2026

Summary

Implement the perry/container and perry/container-compose TypeScript modules, backed by a refactored Rust core and an expanded FFI bridge. This finalises the OCI stack, moving from stubs to a production-hardened implementation.

Changes

Core Subsystems:

  • Orchestration: Implemented ComposeEngine using Kahn's algorithm for
    deterministic dependency resolution and topological startup/shutdown.
  • Backend: Added multi-layered auto-detection for 7+ runtimes (Apple Container,
    Podman, OrbStack, etc.) with liveness checks and priority ordering.
  • Security: Integrated Sigstore/cosign for image verification and hardened
    ephemeral runners with cap_drop: ALL and user: nobody.
  • FFI Bridge: Expanded perry-stdlib with async-safe, promise-based handlers
    and pointer validation, removing legacy block_on calls.

Technical Details:

  • Restructured perry-container-compose crate into a flat module layout.
  • Standardised container naming to {image_hash_8}-{random_hex}.
  • Refactored CliBackend to be generic over CliProtocol for zero vtable
    overhead.
  • Integrated with Perry compiler (HIR registration and codegen dispatch).
  • Added support for seccomp, labels, and read_only modes in ContainerSpec.

Related issue

Test plan

  • Verified via 22 unit tests and 10 property-based tests.

  • Inclusion of Forgejo production deployment example.

  • Fixed SQLite linker conflicts by gating runtime stubs.

  • cargo build --release clean

  • cargo test --workspace --exclude perry-ui-ios --exclude perry-ui-tvos --exclude perry-ui-watchos --exclude perry-ui-gtk4 --exclude perry-ui-android --exclude perry-ui-windows passes

  • (if user-facing) Added or updated a test under test-files/ or a #[test] in the affected crate

  • (if CLI / stdlib / runtime API changed) Updated docs/src/

  • (if touching a platform UI backend) Built -p perry-ui-<backend> locally on that platform

Screenshots / output

Checklist

  • I have NOT bumped the workspace version or edited CLAUDE.md / CHANGELOG.md (maintainer handles these at merge)
  • My commits follow the loose feat: / fix: / docs: / chore: prefix convention used in the log
  • I've read CONTRIBUTING.md and agree to the Code of Conduct

Copy link
Copy Markdown
Contributor

@proggeramlug proggeramlug left a comment

Choose a reason for hiding this comment

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

Hey @yumin-chen — thanks for taking this on, this is a huge piece of work and there's a lot of real substance in here. I did a thorough pass and wanted to share detailed feedback rather than a thumbs-up/down. Given that the PR is still a draft, treat this as "what I'd want to see addressed before I'd be comfortable merging" — not a blocker list, just a map of where I landed.

What's genuinely strong

  • Compose engine with Kahn's algorithm (perry-container-compose/src/compose.rs): correctly topologically sorted, cycle detection, rollback in reverse order. Clean implementation.
  • Backend detection (backend.rs): these are real liveness probes — podman machine list --format json + JSON parse, orb --version, limactl ls --json, socket-existence for Rancher Desktop. Not just which. Per-candidate timeouts are in place. Nice.
  • FFI entry points defensively handle null/malformed input and propagate errors through spawn_for_promise instead of panicking. The "removed block_on" claim checks out — I couldn't find any in the async paths.
  • YAML + env interpolation, type definitions, CLI layer — all look like genuine implementations, not scaffolding.
  • Cosign shell-out is real code (verification.rs), not a stub — it actually runs cosign verify and parses the exit status.

That's a solid 65–75% of the PR being real, working Rust. That's rare at this size.

Things I'd want to see before merge

1. Unrelated changes to lower.rs reopen two closed bugs

In crates/perry-hir/src/lower.rs:

  • Buffer type synonym for Uint8Array removed (~line 9391 and ~9680). Previously function f(src: Buffer) { src[i] } went through the byte-read path; this PR collapses it to Uint8Array only, so code typed with Buffer now returns NaN-boxed pointer bits as denormal f64. The removed comment itself described why the synonym was needed.
  • Expr::ProcessArgv specialization removed from the list-slice fallthrough (~line 8145). The removed comment literally says "closes #41" — so removing it reopens that bug.

I don't think these were intentional — my guess is they slipped in when the branch rebased against an older main. Could you restore both?

2. Unrelated file changes

These don't relate to containers and would be easier to review as separate PRs:

  • crates/perry-runtime/src/closure.rs — removes three SQLite FFI stubs
  • crates/perry-runtime/src/text.rs — cast data_ptr to *const u8 before .add(i) (this looks like a real bug fix, worth its own PR)
  • crates/perry-runtime/src/string.rs — publishing string_as_str + adding impl method
  • src/core/wit/perry-container.wit — WIT file at repo root, outside the usual layout
  • smoke_test.ts at repo root — probably belongs under test-files/
  • example-code/fastify-redis-mysql/myapp — 631KB Mach-O binary, different example directory entirely. Worth adding Mach-O / *.exe globs to .gitignore under example-code/.

3. A few claims that the code doesn't quite back up

Not saying any of this is broken — just that the PR description sets an expectation that I'd want to bring in line with what's shipping:

  • Cosign verification is Chainguard-only. CHAINGUARD_IDENTITY and CHAINGUARD_ISSUER are hardcoded in verification.rs, so images signed by anyone else (Docker Hub, GHCR, private registries) fail verification. Could the identity/issuer be configurable per-image or per-call? Also, VERIFICATION_CACHE caches Failed results — a single rekor timeout will pin that digest as broken for the process lifetime. I'd only cache Verified.
  • alloy_container_run_capability sandboxing is --read-only + --network none (unless granted) + --rm + random name. No seccomp profile (the field exists on ContainerSpec but isn't set here), no --cap-drop, no --user, no resource limits, no tmpfs for /tmp. That's solid restricted-run behavior, but "full sandboxing" in the PR body sets expectations higher. Either over-deliver (seccomp + cap-drop ALL + user nobody + tmpfs /tmp) or adjust the framing.
  • Test count of "22 + 10" actually undercounts — there are ~45 tests. But the meaningful ones lean heavily on MockBackend; live backend and live cosign are gated behind #[cfg(feature = "integration-tests")] so they don't run by default. Would be great to get at least one smoke test per backend running in CI (probably only podman on the Linux runner).

4. A couple of code-shape things

  • Duplicate FFI exports: every compose function has two symbols, e.g. js_container_compose_down AND js_compose_down (container/mod.rs ~569–586 pattern repeats ~9 times). One of them is dead weight — want to pick one shape?
  • Duplicate ContainerSpec: crate::container::types::ContainerSpec lives in perry-stdlib AND perry_container_compose::types::ContainerSpec in the new crate, and capability.rs:35-50 copies field-by-field between them. Feels like an in-progress consolidation.
  • Expr::ExternFuncRef { param_types: Vec::new() } (lower.rs:4843) — empty param_types might cause codegen to skip NaN-box unboxing on string args. Worth a quick codegen-level test (call one of these FFI functions from TS with a string arg, confirm the *const StringHeader on the Rust side is a real pointer, not NaN-boxed bits).

5. Reviewability

This is a lot to land as one PR. If you'd be willing to split it into a series — e.g. (a) perry-container-compose crate in isolation, (b) stdlib FFI bridge, (c) HIR/codegen wiring, (d) capability runner + verification, (e) examples — each of those is reviewable in an afternoon. The current PR is too big for me to sign off on confidently, which I know isn't the feedback anyone wants, but I'd rather say it now than after you've done more work.

TL;DR

The core implementation is real and in a lot of places quite good. The big things I'd ask for before merge are: restore the two lower.rs behaviors this accidentally reverts, pull unrelated file changes and the committed binary out, and either reshape the scope claims (Chainguard-only, restricted-run) or over-deliver on them. Splitting the PR would help a lot.

Thanks again for putting this together — containers are a real gap and it's great to see someone taking a run at it. Happy to pair on any of the above if it'd help.

@yumin-chen yumin-chen force-pushed the feat/container-compose branch 26 times, most recently from b650839 to d4e4fa3 Compare April 27, 2026 12:31
@yumin-chen
Copy link
Copy Markdown
Author

Hi @proggeramlug
Thank you for the detailed and constructive feedback. I have addressed the points raised as follows:

  1. Restored lower.rs behaviors: I've restored the Buffer type synonym for Uint8Array in the member-access path and the Expr::ProcessArgv specialization in the list-slice fallthrough (fixing process.argv.slice(N) returns corrupted data (typeof string, elements are NaN-box bits) #41). These were indeed accidental reversions during a rebase.
  2. Removed unrelated changes: I've reverted the changes to the runtime crate (closure.rs, text.rs, string.rs), removed the errant WIT file, and moved the smoke test. I also added a .gitignore to example-code/ to prevent committing binaries like myapp.
  3. Refined Claims & Sandboxing:
    • Updated VERIFICATION_CACHE to only cache successful verifications to avoid pinning transient network failures.
    • Added VerificationConfig to run_cosign_verify to allow configurable identities/issuers.
    • Hardened perry_container_run_capability by adding --cap-drop ALL and running as nobody.
  4. Code Consolidation:
    • Consolidated duplicate FFI symbols in stdlib/container/mod.rs to a single canonical set.
    • Unified ContainerSpec by re-exporting the definition from the perry-container-compose crate in the stdlib.

I've verified that all container-related tests pass and that the HIR/codegen behaviors are restored. Happy to discuss further or split this into smaller PRs if preferred.

@yumin-chen yumin-chen force-pushed the feat/container-compose branch 2 times, most recently from 8c01733 to d94a290 Compare April 27, 2026 12:55
@yumin-chen yumin-chen force-pushed the feat/container-compose branch 16 times, most recently from 7146845 to 47776d3 Compare April 27, 2026 21:57
Implement the `perry/container` and `perry/container-compose` (workloads)
subsystems, finalising the OCI stack. This transition moves from initial
stubs to a hardened implementation featuring deterministic orchestration
and cross-runtime compatibility.

Core Subsystems:
- Orchestration: Implemented `WorkloadGraphEngine` using Kahn's algorithm for
  topological dependency resolution, deterministic startup, and rollback.
- Backend: Multi-layered auto-detection for 7+ runtimes (Apple Container,
  Podman, OrbStack, etc.) with liveness checks and strict priority ordering.
- Security: Integrated Sigstore/cosign for image verification and hardened
  ephemeral runners with `cap_drop: ALL`, `seccomp`, and `read_only` root.
- FFI Bridge: Expanded `perry-stdlib` with async-safe, promise-based handlers
  optimized for raw C-ABI passing of primitives and validated pointers.

Technical Details:
- Restructured `perry-container-compose` into a flat module layout.
- Refactored `CliBackend` to be generic over `CliProtocol` for zero vtable
  overhead.
- Standardised container naming to `{image_hash_8}-{random_hex}` with
  label-based orphan cleanup logic.
- Modernised internal registries using `DashMap` for improved concurrency.
- Integrated with Perry compiler (HIR registration and codegen dispatch).

Refinements & Fixes:
- Restored `Buffer` synonym and `process.argv` specialization in `lower.rs`.
- Fixed SQLite linker conflicts by gating runtime stubs.
- Implemented robust IP and label extraction for the `DockerProtocol`.
- Added Forgejo production example and exhaustive documentation.
@yumin-chen yumin-chen force-pushed the feat/container-compose branch from 47776d3 to af67749 Compare April 27, 2026 22:10
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.

2 participants