Skip to content

0.11.0#18

Merged
dzikowski merged 86 commits into
mainfrom
nightly
Jul 23, 2026
Merged

0.11.0#18
dzikowski merged 86 commits into
mainfrom
nightly

Conversation

@dzikowski

@dzikowski dzikowski commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
  • Use Jaiph from MCP clients: run jaiph mcp to expose your workflows as tools in Claude Code, Cursor, and other MCP apps — with live progress and the ability to cancel long runs.
  • Pass secrets into sandboxed runs: --env GITHUB_TOKEN (and similar) forwards a host variable into Docker-backed runs when the default allowlist would block it; declare recurring keys in-file with trusted_envs.
  • Smarter config, clearer model setting: config { } values can reference variables and workflow parameters. Breaking: rename agent.default_model to agent.model; it applies per prompt step, not as a global env var.
  • Jaiph on Windows: install with PowerShell, run a native .exe, and get the same workflows without WSL or Docker on win32.
  • Safer Docker sandbox: runs get a writable point-in-time workspace snapshot instead of a live bind mount; gitignored files (.env, node_modules/) never enter the container.
  • Security hardening: release checksums are minisign-signed; run journals are hash-chained with secrets redacted; injected secrets reach only declared run steps, never prompt agents; jaiph mcp isolates the workspace by default, matching jaiph run.
  • Workflow language: else if chains, match pattern alternation ("a" | "b"), and logwarn for yellow warnings in the run tree.

dzikowski and others added 24 commits July 13, 2026 13:00
jaiph run spawns the workflow leader detached and previously stopped it
with process.kill(-pid, signal) — a POSIX-only negative-PID group kill.
On win32 that throws, the child.kill() fallback terminated only the
leader, and the agent backends / script children it spawned were
orphaned.

Add src/runtime/kernel/portability.ts exporting killProcessTree(pid,
signal) as the single sanctioned home for group kills. On POSIX it
preserves prior behavior (negative-PID group kill with a per-process
ESRCH fallback); on win32 it force-kills the whole tree with
`taskkill /pid <pid> /T /F` (spawned, not shelled) via an injectable
seam, degrading to a per-process kill if taskkill cannot launch.
Because taskkill /F is already forceful, the SIGTERM->SIGKILL
escalation is a documented no-op on win32.

Repoint every group-kill call site at the helper: run teardown
(lifecycle.ts), the prompt watchdog (prompt.ts), and the Docker
run-timeout kill (docker.ts). New unit tests stub process.platform to
cover both branches (negative-PID kill on POSIX plus ESRCH fallback;
taskkill /T argv on win32) and prove win32 never calls process.kill
with a negative PID; a src/-wide lint test asserts no production file
outside portability.ts matches `process.kill(-`. Docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Script steps previously ran by spawning the emitted file directly,
relying on the `#!/usr/bin/env <lang>` shebang and the 0o755 exec bit.
Windows honors neither, and `noexec` mounts on Linux strip the exec bit,
both breaking script execution. The runtime already knows the
interpreter since it writes the shebang itself, so `executeScript` now
reads the emitted script's shebang, resolves the interpreter through the
new `resolveInterpreterFromShebang` (`src/parse/script-bash.ts`), and
spawns `<interpreter> <scriptPath> <args...>` explicitly. The shebang
line is still written into every emitted script so they remain directly
executable by hand on POSIX, but the runtime no longer depends on it or
on the exec bit being honored. A spawn ENOENT from a missing interpreter
now surfaces as a diagnosable Jaiph error naming the interpreter instead
of a raw `spawn <name> ENOENT`. New unit tests assert the resolved
interpreter and argv on an injectable `_scriptSpawn` seam, prove a script
with the exec bit stripped (0o644) still runs on POSIX, and prove a
missing-interpreter shebang produces the diagnosable error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline workflow shell lines (executeShLine) and CLI hook commands used a
hardcoded spawn("sh", ["-c", ...]), which fails on win32 where there is no
sh on the default PATH. Add resolveShell() to the portability module as the
single seam both call sites go through: on POSIX it returns bare sh; on
win32 it locates Git for Windows' bundled sh.exe, first on PATH and then in
the standard install layouts (<Git>/bin/sh.exe, <Git>/usr/bin/sh.exe) under
each known root, throwing a diagnosable E_NO_POSIX_SHELL error naming Git
for Windows if none is found. Resolution is memoized per process. Inline
lines are never translated to cmd/PowerShell, so POSIX shell semantics are
unchanged. Unit tests stub process.platform and the PATH/existence lookup
across all branches, and a src/-wide lint test asserts no spawn("sh") call
site remains outside portability.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close three remaining POSIX assumptions that broke a host-only Windows
runtime. prepareClaudeEnv now falls back to os.homedir() when neither
execEnv.HOME nor process.env.HOME is set, so USERPROFILE-only
environments resolve the .claude config dir; an explicit execEnv.HOME
still wins. resolveDockerConfig forces host-only mode on win32 with a
one-line notice (same UX as JAIPH_UNSAFE=true), so the CLI never probes
docker and never hard-fails on a missing daemon, and
JAIPH_DOCKER_ENABLED=true cannot override it. A new canUseAnsi() helper
in the portability module centralizes the isTTY + NO_COLOR gate; every
color/erase emission site routes through it so the policy lives in one
place. Adds unit tests for each and a src-wide lint test asserting no
production file outside portability.ts gates directly on isTTY &&
NO_COLOR. Docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Windows x64 target to the release workflow so every release ships
a fifth standalone binary alongside darwin/linux x arm64/x64. A new
bun-windows-x64 matrix entry (with an ext field so the compiled outfile
and uploaded artifact carry the .exe suffix) produces jaiph-windows-x64.exe;
Bun has no windows-arm64 target so Windows is x64 only. The .exe is
included in SHA256SUMS generation and in both the stable and nightly
gh release upload lists.

A new sanity-windows job runs on windows-latest, downloads the .exe
artifact, and runs jaiph-windows-x64.exe --version through a version
gate; the publish job now needs [build, sanity-windows], so a Windows
version mismatch fails the whole release. The linux-x64 gate's inline
comparison is extracted into a shared, testable
scripts/release-version-check.sh that both gates delegate to.

Update the release asset naming contract in docs/contributing.md and
docs/architecture.md, and add integration/release-workflow.test.ts
asserting the five-binary matrix, SHA256SUMS and upload-list coverage,
the shared gate behavior, and naming-contract/installer parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add docs/install.ps1 as the Windows counterpart to the POSIX docs/install
(run with irm https://jaiph.org/install.ps1 | iex). It downloads
jaiph-windows-x64.exe and SHA256SUMS from the pinned release ref (default
the current stable tag, overridable via JAIPH_REPO_REF / first arg plus
JAIPH_RELEASE_BASE_URL for local file:// dirs used by tests), verifies the
SHA-256 with Get-FileHash and installs nothing on mismatch, then installs
to %LOCALAPPDATA%\jaiph\bin\jaiph.exe (overridable via JAIPH_BIN_DIR), adds
it to the user PATH if absent, and prints the same try-it hints as the bash
installer. Non-x64 / ARM Windows exits non-zero with a documented
unsupported-platform message.

The bash installer now rejects Windows-like uname values and points at the
PowerShell one-liner, and prepare_release.jh rewrites the pinned ref in both
installers in lockstep. CI gains an installer-powershell job on
windows-latest that cross-compiles the real .exe and runs
e2e/tests/installer_powershell.ps1 (checksum mismatch, unsupported arch,
happy-path install with no Node/npm/Bun on PATH); the Docker publish job now
needs it. Host-portable guards live in integration/installer-powershell.test.ts.
Docs updated (setup.md, index.html, architecture.md, contributing.md, README).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The landing page hero install card now offers a Windows PowerShell path
alongside the POSIX curl | bash one-liners. A new .os-switch sub-toggle
(macOS/Linux vs Windows) sits above the run-sample / init-project /
just-install tabs, and each panel wraps its content in .os-variant blocks.

On load, attachOsSwitch() (docs/assets/js/main.js) auto-selects the
Windows variant for Windows visitors via isWindowsPlatform(), which
prefers navigator.userAgentData?.platform and falls back to
navigator.platform. macOS/Linux visitors keep today's POSIX default with
no layout shift; manual switching stays available and is remembered
card-wide. The 'Just install' Windows variant exposes the copy-able
irm https://jaiph.org/install.ps1 | iex line; the run-sample and
init-project tabs (no PowerShell pipe equivalent) show the install
one-liner plus a short 'then run:' jaiph command instead of a bash-only
default. The static-render constraint holds: the POSIX variant carries
is-active in the markup and CSS hides inactive variants, so with JS
disabled all bash one-liners render and no panel is blank.

New Playwright cases in the docs suite assert the Windows default, the
unchanged macOS/Linux default, manual platform + tab switching, the
clipboard contents of the Windows copy button, and the JS-disabled
static render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Config blocks now resolve `${identifier}` and bare-identifier sugar at runtime so workflows can parameterize agent settings like default_model.

Co-authored-by: Cursor <cursoragent@cursor.com>
…lementation)

Spiked MVP of `jaiph mcp <file.jh>` plus design doc and queue tasks.
PARTIAL — do not treat as landed: verification gaps are inventoried in
QUEUE.md ("MCP 1-4/8") and must be closed by the queue task before this
feature is considered done.

Implemented:
- src/cli/mcp/tools.ts     — tool derivation (exports narrowing, route-target
                             exclusion, default→file-slug rename, comment
                             descriptions, string schemas); 10 unit tests
- src/cli/mcp/server.ts    — newline-delimited JSON-RPC 2.0 stdio server
                             (initialize/ping/tools/list/tools/call,
                             list_changed); 16 unit tests
- src/cli/mcp/call.ts      — per-call runner spawn, event capture, result
                             composition (return_value.txt → logs → note)
- src/cli/commands/mcp.ts  — command wiring, hot reload (watchFile), host
                             execution like `jaiph run --raw`
- src/cli/index.ts, usage  — `mcp` subcommand + `--mcp` alias
- runtime: runRoot(name, args) generalizes runDefault; runner dispatches any
  symbol; workflow-launch.ts fix — buildRunModuleLaunch hardcoded "default"
  and dropped the requested workflow symbol
- design/2026-07-14-mcp-server.md — full design; QUEUE.md — grouped task with
  per-piece verification state + ENV --env passthrough task

Verified: tsc + build clean; 26/26 mcp unit tests; live stdio session
(handshake, list, calls incl. -32602 and isError legs, concurrency).

NOT verified (see QUEUE.md acceptance):
- `jaiph run` direction of the workflow-launch fix (touches every run)
- hot reload path (never exercised)
- runRoot/launch argv have no dedicated unit tests
- full `npm test` suite not run since these changes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Land the verification and gap-closing work for the spiked `jaiph mcp`
MVP whose implementation was committed earlier as WIP. Adds the missing
automated coverage and user-facing docs; no runtime behaviour changes.

Tests: pin the launch argv (the workflow symbol must reach the runner
verbatim, guarding the fixed hardcoded-"default" bug, plus the empty
fallback); unit tests for runtime `runRoot` (non-default workflow runs
as root with positional param binding and return_value.txt on success,
unknown workflow returns 1 with no return value, runDefault delegates
unchanged); and an end-to-end scripted stdio session covering
initialize, tools/list with comment-derived descriptions, tools/call
return values, missing-arg -32602, workflow-failure isError, compile
diagnostics to stderr, --help/--mcp alias dispatch, hot reload
(list_changed) and broken-edit tolerance, and a `jaiph run` regression.

Docs: new docs/mcp.md how-to, a `jaiph mcp` reference section in
docs/cli.md, README and docs nav entries, CHANGELOG, and QUEUE cleanup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a repeatable --env flag that lets a workflow receive a specific
host variable outside the fail-closed Docker env allowlist, with the flag
itself standing in as per-key consent. Supports both --env KEY=VALUE
(first = splits, value may contain =, empty allowed) and bare --env KEY
(forwards the host value, aborting with E_ENV_MISSING before spawning if
unset). Names must match [A-Za-z_][A-Za-z0-9_]* or abort with
E_ENV_INVALID, and sandbox-control / runtime-managed keys are refused with
E_ENV_RESERVED.

Semantics are uniform across every execution mode: in host modes the pairs
are applied to the runner env after resolveRuntimeEnv/applySandboxFlags; in
a Docker sandbox they are threaded through DockerSpawnOptions.extraEnv into
buildDockerArgs as explicit -e KEY=VALUE args that bypass isEnvAllowed, each
key emitted exactly once with the --env value winning. On jaiph mcp the
pairs are resolved once at startup and applied to every tool call for the
server's lifetime. Values cross verbatim with no path remapping.

Updates usage strings, docs (cli, env-vars, sandboxing), and adds parse,
buildDockerArgs, resolve-env, MCP, and e2e coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete the MCP how-to and cross-references for serving a .jh file's
workflows as MCP tools over stdio. Add JSON client-config examples
(Claude Desktop, Cursor) alongside the claude mcp add command in
docs/mcp.md, and correct the default exposure rule to match the tree.
Add an MCP server feature note to the landing page (docs/index.html)
and reflect it in the CHANGELOG. Close two test gaps that back
documented behaviour: a non-object JSON message is an invalid request
(-32600) with null id, and toolNameFromFile truncates the derived slug
to 128 characters. Drop the completed task from QUEUE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add black-box e2e coverage of `jaiph mcp` through the real binary
entrypoint (e2e/tests/139_mcp_server_session.sh), wired into
`npm run test:e2e`. The script drives `jaiph mcp <fixture.jh>` as a
child process over stdio with a scripted JSON-RPC session — initialize,
tools/list, a successful tools/call round-tripping a param through the
workflow return, and a failing tools/call — then closes stdin. It
asserts every stdout line is valid JSON-RPC 2.0 (no banner or progress
leakage), the successful tool result text equals the workflow's return
value, the failing workflow yields isError:true rather than a protocol
error, and the server exits 0 on stdin close.

A regression leg asserts `jaiph run` on a default workflow still exits 0
and prints its return value, pinning the shared workflow-launch.ts
launch path in both the named-symbol and default directions after the
hardcoded-`default` fix. Update CHANGELOG.md and drop the completed task
from QUEUE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Long-running `jaiph mcp` tool calls can now stream progress to the client
and be cancelled mid-run. When a `tools/call` carries a
`params._meta.progressToken`, each of the run's STEP_START/STEP_END events
is translated into a `notifications/progress` with a monotonically
increasing counter; notifications stop the moment the call's response is
sent, and calls without a progressToken emit none. A
`notifications/cancelled` for an in-flight request id terminates that
call's child process tree via the new `cancelRunProcess` in
lifecycle.ts (SIGINT then an unref'd force-kill timer escalating to
SIGKILL), sends no response for the cancelled id, and keeps the server
serving. Step events and the child terminator are surfaced through a new
per-call `McpCallContext` (`onStep` / `onCancelHandle`), wired by
`attachOutputCollector` off the existing `parseStepEvent` stderr stream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs-lint caught the only failure in the full suite after the MCP queue
landed: cli.md linked mcp.md#stream-progress-and-cancel-a-long-call but the
heading is numbered ("## 7. Stream progress and cancel a long call"), so the
anchor needs the 7- prefix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…el to prompts only.

In-file agent.model no longer maps into JAIPH_AGENT_MODEL — it resolves per prompt
and is passed as --model to the backend CLI. JAIPH_AGENT_MODEL remains a shell-only
run-wide override. Parser, runtime, tests, docs, and in-repo .jh modules updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
…onfigs.

install-from-local.sh builds runtime/Dockerfile and tags ghcr.io/jaiphlang/jaiph-runtime:<version> plus :nightly so sandbox runs match the checkout without JAIPH_DOCKER_IMAGE. Standalone CLI resolves the default image tag from embedded VERSION when package.json is absent. Update .jaiph workflows with per-step agent.model and claude backend defaults; queue 0.11.0 release task.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a #dev-ready task to render prompt steps as `prompt <backend> <model> "…"` in the live run tree, using the already-resolved model from prompt invocation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a #dev-ready task requiring bare identifier, quoted, and bare ${ref}
forms to be equivalent everywhere the language accepts orchestration strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a #dev-ready task to fix orphaned containers after SIGINT and extend e2e beyond sandbox-dir cleanup to assert the container exits.

Co-authored-by: Cursor <cursoragent@cursor.com>
…s scope.

Document a #dev-ready UX task for unsafe host-only consent, clearer workspace-vs-whole-machine warnings, and the inplace typo fix.

Co-authored-by: Cursor <cursoragent@cursor.com>
Accept bare ${name} interpolation refs everywhere Jaiph string RHS parsers
already allow bare identifiers or quoted strings. ensure_ci_passes unsets
inherited JAIPH_* variables before npm run test:ci so agent workflow env
does not leak into integration tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
@dzikowski dzikowski changed the title 0.10.1 0.11.0 Jul 15, 2026
dzikowski and others added 5 commits July 15, 2026 16:54
Cut v0.11.0 from the Unreleased changelog block. Bump package.json and
package-lock.json to 0.11.0, refresh the pinned installer ref v0.10.0 ->
v0.11.0 in both docs/install and docs/install.ps1, and stamp CHANGELOG.md:
rename Unreleased -> 0.11.0 with a new Summary section (MCP server + Docker
parity, --env passthrough, config interpolation + agent.model breaking
changes, Windows portability/distro) above the existing All changes bullets,
leaving a fresh empty Unreleased section at the top. Update remaining
user-facing "current release" literals from 0.10.0 to 0.11.0 across
docs/index.html, README.md, docs/setup.md, docs/env-vars.md, docs/cli.md,
and docs/contributing.md, and drop the completed release task from QUEUE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each NodeWorkflowRuntime run now clears the process-local mock response
queue so identical JAIPH_MOCK_RESPONSES_JSON does not exhaust shared mocks
and fall through to real backends with long retry backoff. resolveRuntimeEnv
sets JAIPH_AGENT_MODEL to "" when unset so scripts under set -u do not fail.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace implementation-heavy bullets with a short, human-facing feature
summary for MCP, --env, config/model changes, and Windows support.

Co-authored-by: Cursor <cursoragent@cursor.com>
Nightly prereleases are branch-driven only; the redundant git tag conflicted
with refs/heads/nightly and broke plain git push.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prompt steps in the live run tree and non-TTY step labels now render the
effective model as a bare token between the backend and the quoted
preview: `▸ prompt claude sonnet "Classify this task…"` on start and
`✓ prompt claude sonnet (5s)` on completion. The model is the value
already passed to the backend for that invocation (whatever
`resolveModel` returns with a non-empty string); the token is omitted
when the backend auto-selects, falling back to the prior two-token
`prompt <backend> "…"` form. A custom `agent.command` still shows the
command basename with the model appended after it.

The resolved model is threaded to the display layer as a new `model`
field on the `STEP_START`/`STEP_END` events and parsed into
`StepEvent.model`, so the display formatters render the three-part label
without re-reading `PROMPT_START`. Existing truncation rules (24-char
preview, 96-char line cap) are unchanged. This is CLI/run-tree
presentation only — the `.jh` language is untouched and
`config { agent.model = … }` remains the authoring surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dzikowski and others added 28 commits July 20, 2026 14:04
Each line written to run_summary.jsonl now carries a `prevHash` field
computed from the SHA-256 of the previous line's raw payload, forming a
tamper-evident hash chain.  A misbehaving agent that rewrites or truncates
its own audit trail is detectable by walking the chain.  Known credential
env-var values (ANTHROPIC_API_KEY and the Docker/backend allowlist) are
redacted from persisted prompt bodies and reconstructed command lines
before any artifact write.  New unit tests cover chain verification
(tamper detection) and redaction of fixture secrets.  Architecture and
artifacts docs describe the chain format and the `jaiph verify-run`
recipe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses ASI-09 (LOW) from the 2026-07-20 security review. The install
scripts now verify a cosign detached signature over SHA256SUMS before
trusting any checksum, failing closed when the signature file is absent
or invalid. runtime/Dockerfile replaces all curl-pipe-to-shell toolchain
installs with pinned version URLs plus inline SHA-256 verification,
ensuring no unverified remote script is executed during image builds.
CI gains a release-workflow integration test covering the signature
round-trip, the e2e installer test asserts the fail-closed path, and
docs/contributing.md + docs/setup.md document the cosign trust model and
verification steps for end users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Imported-module metadata can no longer override execution-config keys
(agent.command, agent.backend, and their JAIPH_AGENT_COMMAND/BACKEND
equivalents) via applyMetadataScope. Only the entry module's config may
set those keys for a run. The existing *_LOCKED env gates are preserved;
defaults are now safe without requiring callers to pre-lock. Adds
tests (unit + e2e) covering the trust boundary: an imported module that
sets agent.command is silently ignored while the entry module's setting
is still honoured. Docs updated to note the execution-config trust
boundary for imported libraries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously ENV_ALLOW_PREFIXES forwarded entire ANTHROPIC_*/CURSOR_*/CLAUDE_*/OPENAI_*
host env families into the container, exposing unrelated secrets to sandboxed
code. Replace prefix forwarding for credentials with an enumerated
BACKEND_CREDENTIAL_KEYS map and scope isEnvAllowed to the resolved agent
backend(s) for the run; JAIPH_* run-control keys keep prefix forwarding since
the runtime itself needs them. --env remains the documented escape hatch for
anything else. Updates preflight-credentials to check against the resolved
backend and adds unit tests locking the forwarded key set per backend.
…dening

Overlay mode starts as root with SYS_ADMIN, SETUID/SETGID, CHOWN,
DAC_READ_SEARCH, and (on Linux) apparmor=unconfined to mount
fuse-overlayfs, then drops privileges before workflow code runs. That is
a larger kernel attack surface than copy mode for the same isolation
guarantee. After re-evaluating, overlay stays the default on fuse hosts
because copy pays a full workspace clone per run while overlay starts in
O(1); the tradeoff is now spelled out in docs/sandboxing.md and
docs/sandbox-run.md, including when to force JAIPH_DOCKER_NO_OVERLAY=1.
The apparmor=unconfined exception is called out as an explicit tracked
follow-up (new QUEUE.md item for a tailored AppArmor profile), and new
posture-lock tests in src/runtime/docker.test.ts pin the exact cap set,
security-opt set, and per-mode start UID so future changes can't widen
the posture silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Jakub Dzikowski <jakub.t.dzikowski@gmail.com>
Add a deterministic git.push(branch) step so the credentialed push is
performed by trusted runtime code rather than the LLM agent. With an
explicit branch it force-targets HEAD:refs/heads/<branch> so the push
cannot be retargeted; gh_ci_passes.jh now has the agent commit locally
only and pushes via run git.push(branch).

Queue: reword the credential task to Move 1 only (scrub secrets from
prompt subprocess env, .jaiph out of scope) and add a standalone
trusted_envs feature spec (host-snapshot resolution, trusted-run-only,
entry-file-only module lock).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Write placeholder SHA256SUMS.minisig files in the PowerShell installer
  e2e test so it proceeds past the signature-download step to the
  checksum verification the test actually targets.
- Select the last (not first) ```text block in the docs tutorial task-6
  test, since the tutorial now shows an extra stderr-only prompt block
  before the real post-run output.
Docker-gated e2e tests self-check `docker info` and run regardless of
matrix.label, so the "host" job still exercises Docker-sandboxed legs.
Overlay must be disabled for any Ubuntu GHA runner, not just the
"docker" label.
The prior docs parity update edited docs/jaiph-skill.md without rerunning
tools/embed-assets.js, leaving the checked-in JAIPH_SKILL_MD_BASE64 blob
stale and failing the embedded-assets drift check in CI.
…exclude)

Preserve JAIPH_DOCKER_NO_OVERLAY through e2e env scrub so CI's no-overlay
setting actually reaches tests, and exclude the configured runs root from
sandbox workspace clones so GNU cp does not recurse into itself when
JAIPH_RUNS_DIR is nested inside the workspace.

Co-authored-by: Cursor <cursoragent@cursor.com>
Regression for the GNU cp self-copy failure when copy-mode allocates
.sandbox-* under a workspace-relative runs root.

Co-authored-by: Cursor <cursoragent@cursor.com>
Previously an --env-forwarded secret (e.g. `jaiph run --env GITHUB_TOKEN
.jaiph/gh_ci_passes.jh`) leaked into the LLM agent's environment. The
prompt backend inherited the full workflow env verbatim: runBackend
defaulted childEnv to execEnv (scope.env — process.env plus everything
merged from --env), and prepareClaudeEnv only augmented the env rather
than stripping it, so the claude subprocess (spawned with
bypassPermissions and fed untrusted content) received GITHUB_TOKEN and
every other --env secret.

Under the trust model only deterministic author-written `run` steps need
credentials; `prompt` hands control to the model, so no prompt step
legitimately needs an --env-injected secret and the scrub is applied
unconditionally. Every prompt backend is now spawned through the new
scrubPromptEnv(execEnv, backend), forwarding only the base environment a
CLI needs (PATH, HOME, locale, TLS/proxies, CLAUDE_CONFIG_DIR, XDG/Windows
basics), JAIPH_* control keys, and the agent's own backend credential keys
— dropping everything else fail-closed. The existing isEnvAllowed /
BACKEND_CREDENTIAL_KEYS / ENV_ALLOW_* / RUN_WORKFLOW_ENV allowlist was
lifted from src/runtime/docker.ts into a new kernel module
src/runtime/kernel/env-allowlist.ts (re-exported from docker.ts), so one
fail-closed policy now governs prompt subprocesses in all sandbox modes,
host mode included, where previously there was no allowlist at all.

Tests cover scrubPromptEnv, the env handed to spawned prompt backends
(host mode for cursor/claude and Docker copy mode), and a regression that
a trusted `run` script step still receives the full --env value. Docs
updated in docs/sandboxing.md and docs/env-vars.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GitHub rejects 0-byte release assets with HTTP 400 Bad Content-Length.
Only attach a real minisign signature, and drop any stale one when unsigned.

Co-authored-by: Cursor <cursoragent@cursor.com>
Two dev-ready tasks from the fs-isolation redesign. First: replace the
fuse-overlay/copy duality with a single host-side snapshot mechanism
(point-in-time, CoW-first via clonefile/reflink, zero added caps, snapshot
under .jaiph/runs/<run>/sandbox with a tmpfs mask over the run mount),
pinning host-change resistance as the contract and enumerating an
exhaustive, grep-verified deletion inventory for the fuse path. Second:
restrict snapshot content to what git sees (ls-files + .git wholesale) so
gitignored secrets never enter the sandbox and artifact dirs stop
dominating copy cost; non-git workspaces keep copy-everything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Workflows can now list required host env keys in config instead of relying
on --env; values reach trusted run steps only and never prompt subprocesses.
Also switch engineer, qa, and security_review workflows from fable to opus.

Co-authored-by: Cursor <cursoragent@cursor.com>
The default Docker mode now gives the container a writable point-in-time
snapshot of the workspace, cloned host-side at run start (copy-on-write
where the filesystem supports it) and bind-mounted read-write. Host edits
during a run stay invisible to the container, container workspace writes
are discarded at exit, and the live host workspace is never mounted at
all. The snapshot lives at <run dir>/sandbox, is masked from the
container's own /jaiph/run view by a tmpfs, and is purged on exit unless
JAIPH_DOCKER_KEEP_SANDBOX is set.

SandboxMode collapses to "snapshot" | "inplace"; selectSandboxMode is now
just JAIPH_INPLACE-or-snapshot with no /dev/fuse probing. This removes
fuse-overlayfs entirely along with its elevated setup posture (root
start, SYS_ADMIN/SETUID/SETGID/CHOWN/DAC_READ_SEARCH cap-adds,
apparmor=unconfined, setpriv drop), the JAIPH_DOCKER_NO_OVERLAY escape
hatch, JAIPH_HOST_UID/GID, E_DOCKER_OVERLAY, runtime/overlay-run.sh and
its embedded asset, and the fuse-overlayfs/fuse3 image packages. Snapshot
and inplace now share one minimal posture (cap-drop ALL, no cap-adds,
no-new-privileges, no device, no apparmor opt, host uid:gid on Linux),
pinned by posture-lock tests. The run banner reads
(Docker sandbox, snapshot). Docs, CI matrix, and e2e are rewritten to the
new model, with a new snapshot-isolation e2e test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dbox

The default Docker snapshot no longer copies the whole workspace. For a
git workspace, cloneWorkspaceForSandbox now derives the snapshot's file
set from git — `git ls-files -z --cached --others --exclude-standard`
(tracked plus untracked-but-not-ignored) plus the `.git/` directory
wholesale — so gitignored paths are absent from the container, not
empty: a `.env`, an `.npmrc` token, a built `dist/`, and a
`node_modules/` tree are never copied and never scanned. git is the sole
ignore oracle (no reimplemented matcher), the copy walk prunes at
directory granularity, and the content is identical across every
platform and copy mechanism (APFS clonefile, reflink, plain copy).

Submodule gitlinks are copied wholesale as opaque subtrees;
tracked-but-deleted worktree paths are skipped; the runs-root exclusion
still applies. A non-git workspace (no `.git` root, or `git ls-files`
fails) falls back to copying everything. There is no config escape
hatch, so a workflow that builds or tests installs its dependencies
inside the container, exactly as a fresh CI checkout would. Applies to
the Docker snapshot path only; `--inplace` and host modes are untouched.

Adds unit coverage in docker.test.ts (mechanism-independent content,
nested `.gitignore` `!` negation matches git, `.git/` present and
functional, non-git fallback, deleted-file tolerance) and e2e
74e_docker_git_snapshot_content.sh; documents the model in
docs/sandboxing.md, architecture.md, and sandbox-run.md.
…elease.

Ship jaiph.pub as the canonical signing key, default both installers to its RW line, ignore jaiph.key, and extend the release-prep workflow with a prompt step that stamps CHANGELOG.md before bumping version.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Jakub Dzikowski <jakub.t.dzikowski@gmail.com>
…image

Add design/2026-07-23-serve-http-api.md: `jaiph serve <file.jh>` exposes
workflows as an HTTP API with a generated OpenAPI 3.1 document and Swagger
UI, reusing the MCP call layer and the durable run journal for SSE run
inspection. Queue five dev-ready tasks: serve core, run inspection
endpoints, OTLP trace export, Sentry error reporting, and the runtime
image as a standalone docker/k8s runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge post-0.10.0 notes into a single 0.11.0 section, keep registry:build working when jaiphlang/registry is unavailable, and fix prepare_release fenced-script closers.

Co-authored-by: Cursor <cursoragent@cursor.com>
GitHub-hosted ubuntu-latest runners need root for apt; the inline apt-get fallback failed with permission denied and blocked SHA256SUMS.minisig signing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Harden CI signing for passwordless and encrypted keys, sync jaiph.pub
and installer defaults to the new key, and trim release-signing docs.
Drop passphrase handling for passwordless CI keys and reject the public
key or incomplete secrets before minisign hits base64 decode errors.
minisign -W still writes an "encrypted secret key" header; pipe an empty
password instead of rejecting the key as passphrase-protected.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the long per-feature paragraphs with concise release highlights covering MCP, secrets, config, Windows, sandbox, security, and language changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Satisfies the release-workflow test that requires rotation guidance in the Release signing section.

Co-authored-by: Cursor <cursoragent@cursor.com>
@dzikowski
dzikowski merged commit 298b466 into main Jul 23, 2026
17 checks passed
@dzikowski
dzikowski deleted the nightly branch July 23, 2026 18:31
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