Skip to content

feat: slim sandbox images + multi-harness restructure (bd openlock-8op)#47

Merged
vessux merged 20 commits into
mainfrom
feat/slim-images
May 29, 2026
Merged

feat: slim sandbox images + multi-harness restructure (bd openlock-8op)#47
vessux merged 20 commits into
mainfrom
feat/slim-images

Conversation

@vessux
Copy link
Copy Markdown
Owner

@vessux vessux commented May 29, 2026

Summary

Foundation work toward an eventual v0.9.0. Replaces the 4-cap image matrix (openlock-core{,-js,-py,-js-py}) with a single ghcr.io/vessux/openlock-base image + per-project editable .openlock/Containerfile. Version stays at v0.8.0; changelog accumulates under "Unreleased" until release.

Highlights

  • Single base image (~310MB): ubuntu + node tarball-slim + python3 + uv + corepack + sandbox user
  • Per-arch SHA256 pinning for upstream node + uv tarballs (defense-in-depth)
  • .openlock/Containerfile is the new source of truth — seeded by openlock, editable by user
  • Halves final sandbox image size (~640MB vs ~1.4-1.57GB measured)
  • New top-level CLI: openlock update-base, openlock prune-images [--legacy], openlock --print-base-tag
  • Caps detection removed; every sandbox has node + python ready

Breaking changes (deferred until v0.9.0 ships)

  • caps field in .openlock/config.yaml: warn-and-ignore (validate --fix cleanup arrives with lvc.2)
  • Pre-v0.9 sandbox images orphaned — openlock prune-images --legacy reclaims disk

Test plan

  • Unit tests: 588 pass / 8 skip / 0 fail (bun test)
  • Typecheck clean (bun run typecheck)
  • Lint + knip clean (bun run lint && bun run knip)
  • Live integration smoke: OPENLOCK_LIVE_INTEGRATION=1 bun test tests/integration/slim-images.test.ts — 2/2 passing on Mac/podman (base + sandbox with claude_code)
  • Reviewer to spot-check on Linux/docker before merge
  • Reviewer to validate migration: take a project with pre-v0.9 .openlock/ (config + policy only), run openlock sandbox, verify Containerfile seeded
  • Reviewer to validate update-base round-trip preserves a custom RUN in the harness section

Out of scope (separate PRs, build toward v0.9.0 release)

  • lvc.1 — interactive openlock init wizard (uses seedContainerfile())
  • lvc.2openlock validate --fix (removes deprecated caps field)
  • lvc.3openlock doctor checks for base drift + stale image inventory
  • awo — CI workflow pushing ghcr.io/vessux/openlock-base:<hash> (multi-arch buildx)
  • Additional harnesses (pi, codex, gemini-cli) — extend HARNESS_FRAGMENTS in seed-containerfile.ts

Spec: docs/superpowers/specs/2026-05-29-slim-images-design.md (gitignored, local-only)
Plan: docs/superpowers/plans/2026-05-29-slim-images.md (gitignored, local-only)

vessux added 17 commits May 29, 2026 13:25
Single image: ubuntu + node (tarball-slim) + python3 + uv + corepack +
sandbox user. Replaces 4-cap matrix. Tagged as
ghcr.io/vessux/openlock-base:<sha256(content)[0..12]> by CI.

Refs bd openlock-8op.
Defense-in-depth: TLS trust alone isn't enough for a security-focused
sandbox tool. Per-arch SHA256 from nodejs.org/dist/v22.12.0/SHASUMS256.txt
and github.com/astral-sh/uv/releases/download/0.5.11/.

Refs bd openlock-8op.
Single base image carries node + python + uv, so policy allowlists both
npm and pypi unconditionally. Paths updated for tarball node install
(/usr/local/bin/node) and uv-based python tooling (no pip/venv).

Refs bd openlock-8op.
Callers of defaultPolicyContent(caps) and policyKeyForCaps will be fixed
in subsequent tasks (T8 rewrites openlock-folder.ts; T10 deletes
detect-caps.ts).

Refs bd openlock-8op.
Pure function generating the per-project Containerfile from harness
selection + embedded base content + base hash. Snapshot-tested per
harness combo.

Refs bd openlock-8op.
Embeds base.Containerfile content at build time, computes ghcr-qualified
tag from sha256, pulls from ghcr (fast path) or builds locally (dev mode
/ offline). Local builds get the same ghcr-qualified tag so user FROM
lines resolve identically in both modes.

Refs bd openlock-8op.
For FROM lines starting with ghcr.io/vessux/openlock-base:, resolve the
base before building user's Containerfile. For third-party FROMs, defer
pull to runtime. Embeds containers/base.Containerfile at compile time.

Refs bd openlock-8op.
Sentinel-based harness-block preservation. Refuses to clobber if
sentinel missing (user heavily restructured file). Shares HARNESS_SENTINEL
const with seed-containerfile.ts.

Refs bd openlock-8op.
- OpenlockFolderConfig no longer carries 'caps' (becomes warn-and-ignore
  via deprecations[]).
- New 'restored-containerfile' origin: writes seed Containerfile on next
  sandbox create when only config+policy exist.
- Deprecation warning surfaces on sandbox create.
- session.ts now derives caps via detectCaps(projectPath) (load-bearing
  for image build until T9/T10 cut over).
- npm-scoped-pkg integration test updated for caps-less defaultPolicyContent().

Refs bd openlock-8op.
- build-images.ts (update-images command): single ensureBase call,
  replacing 4-cap loop.
- session.ts buildSandboxImage: reads user's .openlock/Containerfile and
  calls ensureSandbox, replacing cap-keyed DEFAULT_CONTAINERFILES lookup.

Sandbox-create critical path is now M5-native.

Refs bd openlock-8op.
- detect-caps.ts: no longer needed; single base image
- default-containerfiles.ts: replaced by .openlock/Containerfile
- containers/core*.Containerfile: replaced by base.Containerfile
- policies/default-{js,py,js-py}.yaml: collapsed into default.yaml
- scripts/render-default-policies.ts: render only single default.yaml,
  updated paths to /usr/local/bin/node etc.
- session.ts/session-store.ts: strip caps from runtime + persisted state;
  legacy meta files with caps load silently (caps dropped on read).
- integration tests: switch from DEFAULT_CONTAINERFILES + containerfileKeyForCaps
  to BASE_CONTAINERFILE + ensureImage; harness-binary test now layers
  claude_code via seedContainerfile + ensureSandbox.

Refs bd openlock-8op.
CI invokes this to compute the canonical ghcr tag from the embedded
base.Containerfile, avoiding reimplementation of computeBaseTag in
shell.

Refs bd openlock-8op.
Rewrites .openlock/Containerfile FROM line to the current embedded base
hash, preserving the harness block via sentinel matching. Top-level
command (not sandbox subcommand) because the existing sandbox command
doesn't support sub-routing.

Refs bd openlock-8op.
Default mode removes stale openlock-sandbox tags (not referenced by an
active container) + non-current ghcr.io/vessux/openlock-base tags. With
--legacy, removes pre-M5 openlock-core* images. In-use detection via
podman/docker ps (no session-store coupling).

Refs bd openlock-8op.
The ln -sf /usr/bin/claude /usr/local/bin/claude lines were inherited
from the old NodeSource-deb base (npm prefix /usr → binaries at /usr/bin).
M5's tarball-slim node uses /usr/local prefix, so npm itself creates the
correct /usr/local/bin/claude symlink — the ln -sf was clobbering it
with a dangling target.

Caught by the new live integration smoke test (gated behind
OPENLOCK_LIVE_INTEGRATION=1) which verifies the full pipeline:
- ensureBase() builds base.Containerfile
- seedContainerfile + ensureSandbox produces working sandbox image
- claude binary resolves at /usr/local/bin/claude

Refs bd openlock-8op.
Comment thread src/cli/update-base.ts Fixed
This PR is the base for an eventual v0.9.0 but isn't the release itself.
More features (lvc.1 init wizard, lvc.2 validate, lvc.3 doctor, awo
ghcr push) will accumulate under 'Unreleased' before the version bump.
@vessux vessux changed the title feat: v0.9.0 — slim images + multi-harness restructure (bd openlock-8op) feat: slim sandbox images + multi-harness restructure (bd openlock-8op) May 29, 2026
vessux added 2 commits May 29, 2026 15:24
Moving to a release-time-only changelog pattern: feature PRs don't
touch CHANGELOG.md; the release PR aggregates merged PRs since the
last tag into a single curated entry. Avoids mid-cycle changelog
churn and the conflict risk of an "Unreleased" section without losing
curation.
Three issues found by CI on PR #47:

- containers/base.Containerfile: add `mkdir -p /sandbox/repo` after the
  user switch. Old core.Containerfile had this; dropped during T1 review
  as a deferred concern, but post-create-exec-proxy.test.ts fails with
  "cd: /sandbox/repo: No such file or directory" when the bind-mount
  target doesn't pre-create the dir.

- src/cli/update-base.ts: TOCTOU race flagged by CodeQL (high). Drop
  existsSync + readFileSync pair; use try/catch on readFileSync with
  ENOENT check. Same fix pattern as project_handover_2026_05_11.md.

- tests/integration/slim-images.test.ts: was hardcoded to `podman run`,
  failed on the docker CI matrix (exit 125). Use resolveRuntime() so the
  test matches the runtime that built the image.

Refs bd openlock-8op, PR #47.
@vessux vessux merged commit c5680e1 into main May 29, 2026
5 checks passed
@vessux vessux deleted the feat/slim-images branch May 29, 2026 13:57
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