foldyard 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- foldyard-0.0.1/.github/workflows/foldyard-e2e.yml +209 -0
- foldyard-0.0.1/.github/workflows/foldyard.yml +26 -0
- foldyard-0.0.1/.github/workflows/release.yml +53 -0
- foldyard-0.0.1/.gitignore +21 -0
- foldyard-0.0.1/CHANGELOG.md +27 -0
- foldyard-0.0.1/CLAUDE.md +1 -0
- foldyard-0.0.1/CONTRIBUTING.md +30 -0
- foldyard-0.0.1/DEVELOPMENT.md +324 -0
- foldyard-0.0.1/LICENSE +202 -0
- foldyard-0.0.1/NOTICE +6 -0
- foldyard-0.0.1/PKG-INFO +168 -0
- foldyard-0.0.1/README.md +133 -0
- foldyard-0.0.1/SECURITY.md +33 -0
- foldyard-0.0.1/docs/README.md +43 -0
- foldyard-0.0.1/docs/adrs/0001-rootless-podman-vm-isolation-boundary.md +96 -0
- foldyard-0.0.1/docs/adrs/0002-stack-colocation-project-as-isolation-unit.md +86 -0
- foldyard-0.0.1/docs/adrs/0003-name-foldyard.md +69 -0
- foldyard-0.0.1/docs/adrs/0004-one-machine-worktrees-as-compose-projects.md +84 -0
- foldyard-0.0.1/docs/adrs/0005-secretless-by-default-posture-axes.md +78 -0
- foldyard-0.0.1/docs/adrs/0006-host-side-enforcement-single-supervisor.md +82 -0
- foldyard-0.0.1/docs/adrs/0007-credential-injection-at-egress-proxy.md +86 -0
- foldyard-0.0.1/docs/adrs/0008-keyless-agent-auth.md +85 -0
- foldyard-0.0.1/docs/adrs/0009-monitoring-cooperative-enforcement-locked.md +95 -0
- foldyard-0.0.1/docs/adrs/0010-podman-everywhere-container-host.md +87 -0
- foldyard-0.0.1/docs/adrs/0011-machine-backends-one-socket-contract.md +140 -0
- foldyard-0.0.1/docs/adrs/0012-uv-tool-distribution-no-mutable-daemon-source.md +94 -0
- foldyard-0.0.1/docs/adrs/0013-in-repo-carve-out-until-extraction.md +79 -0
- foldyard-0.0.1/docs/adrs/0014-consumer-supplied-box-image.md +117 -0
- foldyard-0.0.1/docs/adrs/0015-plugin-framework-per-consumer-registry.md +89 -0
- foldyard-0.0.1/docs/adrs/0016-per-worktree-posture.md +91 -0
- foldyard-0.0.1/docs/adrs/0017-nested-virt-validation-strategy.md +87 -0
- foldyard-0.0.1/docs/adrs/0018-zed-editor-rejected.md +47 -0
- foldyard-0.0.1/docs/adrs/0019-consumer-repo-plugins-trust-model.md +92 -0
- foldyard-0.0.1/docs/adrs/0020-post-extraction-consumption-model.md +131 -0
- foldyard-0.0.1/docs/adrs/0021-per-kernel-git-index-split.md +105 -0
- foldyard-0.0.1/docs/adrs/0022-host-runs-the-adopted-config.md +140 -0
- foldyard-0.0.1/docs/adrs/0023-no-host-executed-code-from-the-repo-mount.md +164 -0
- foldyard-0.0.1/docs/adrs/0024-declarative-consumer-axes-no-repo-path-plugins.md +122 -0
- foldyard-0.0.1/docs/adrs/README.md +46 -0
- foldyard-0.0.1/docs/compose-overlays.md +88 -0
- foldyard-0.0.1/docs/configuration.md +713 -0
- foldyard-0.0.1/docs/lima-backend-scope.md +63 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/README.md +222 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/agent-claude.sh +69 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/install.sh +147 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/proxy.py +168 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/run.sh +44 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/test.sh +72 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/transparent/README.md +47 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/transparent/install-cwall.sh +117 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/transparent/run-cwall.sh +35 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/transparent/test-cwall.sh +52 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/transparent/tproxy.py +169 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/wall-nested.yaml +68 -0
- foldyard-0.0.1/docs/lima-network-forcing-kit/wall.yaml +51 -0
- foldyard-0.0.1/docs/lima-wall-machine-integration.md +200 -0
- foldyard-0.0.1/docs/mode-state-consolidation.md +165 -0
- foldyard-0.0.1/docs/modes.md +194 -0
- foldyard-0.0.1/docs/nested-virt.md +179 -0
- foldyard-0.0.1/docs/networking.md +112 -0
- foldyard-0.0.1/docs/podman-multi-vm-issue-26281.md +34 -0
- foldyard-0.0.1/docs/prior-art.md +112 -0
- foldyard-0.0.1/docs/quickstart.md +132 -0
- foldyard-0.0.1/docs/security.md +184 -0
- foldyard-0.0.1/docs/testing-modes.md +86 -0
- foldyard-0.0.1/example/.gitignore +2 -0
- foldyard-0.0.1/example/README.md +114 -0
- foldyard-0.0.1/example/api/Dockerfile +14 -0
- foldyard-0.0.1/example/api/main.py +39 -0
- foldyard-0.0.1/example/box.Dockerfile +25 -0
- foldyard-0.0.1/example/compose.feature.yml +8 -0
- foldyard-0.0.1/example/compose.yml +30 -0
- foldyard-0.0.1/example/foldyard.toml +76 -0
- foldyard-0.0.1/example-lima-wall/.gitignore +2 -0
- foldyard-0.0.1/example-lima-wall/README.md +79 -0
- foldyard-0.0.1/example-lima-wall/box.Dockerfile +17 -0
- foldyard-0.0.1/example-lima-wall/compose.yml +47 -0
- foldyard-0.0.1/example-lima-wall/foldyard.toml +85 -0
- foldyard-0.0.1/example-lima-wall/test_network.sh +175 -0
- foldyard-0.0.1/foldyard.toml +37 -0
- foldyard-0.0.1/justfile +134 -0
- foldyard-0.0.1/pyproject.toml +203 -0
- foldyard-0.0.1/src/foldyard/__init__.py +11 -0
- foldyard-0.0.1/src/foldyard/__main__.py +9 -0
- foldyard-0.0.1/src/foldyard/allowlist.py +470 -0
- foldyard-0.0.1/src/foldyard/assets/box/Dockerfile +39 -0
- foldyard-0.0.1/src/foldyard/assets/box/git-index-shim.sh +285 -0
- foldyard-0.0.1/src/foldyard/assets/machine-wall/machine-wall.sh +235 -0
- foldyard-0.0.1/src/foldyard/assets/proxy/egress_proxy.py +743 -0
- foldyard-0.0.1/src/foldyard/assets/skills/bootstrap-devbox/SKILL.md +134 -0
- foldyard-0.0.1/src/foldyard/assets/skills/foldyard/SKILL.md +84 -0
- foldyard-0.0.1/src/foldyard/assets/skills/foldyard/references/egress-and-capture.md +50 -0
- foldyard-0.0.1/src/foldyard/assets/skills/foldyard/references/posture-and-credentials.md +54 -0
- foldyard-0.0.1/src/foldyard/assets/skills/foldyard/references/when-a-change-doesnt-take.md +32 -0
- foldyard-0.0.1/src/foldyard/box.py +1112 -0
- foldyard-0.0.1/src/foldyard/browser.py +40 -0
- foldyard-0.0.1/src/foldyard/cli.py +816 -0
- foldyard-0.0.1/src/foldyard/config.py +1557 -0
- foldyard-0.0.1/src/foldyard/configpin.py +868 -0
- foldyard-0.0.1/src/foldyard/devmode.py +1484 -0
- foldyard-0.0.1/src/foldyard/docs.py +129 -0
- foldyard-0.0.1/src/foldyard/exposure.py +483 -0
- foldyard-0.0.1/src/foldyard/githeal.py +240 -0
- foldyard-0.0.1/src/foldyard/init.py +486 -0
- foldyard-0.0.1/src/foldyard/keyless.py +371 -0
- foldyard-0.0.1/src/foldyard/machine.py +480 -0
- foldyard-0.0.1/src/foldyard/machine_backend.py +649 -0
- foldyard-0.0.1/src/foldyard/plugins/__init__.py +898 -0
- foldyard-0.0.1/src/foldyard/plugins/_passthrough_bundles.py +246 -0
- foldyard-0.0.1/src/foldyard/plugins/auth0_sim.py +157 -0
- foldyard-0.0.1/src/foldyard/plugins/claude.py +187 -0
- foldyard-0.0.1/src/foldyard/plugins/codex.py +242 -0
- foldyard-0.0.1/src/foldyard/plugins/codex_chatgpt_token.py +187 -0
- foldyard-0.0.1/src/foldyard/plugins/fakecred.py +121 -0
- foldyard-0.0.1/src/foldyard/plugins/fakecred_minter.py +64 -0
- foldyard-0.0.1/src/foldyard/plugins/gcp.py +639 -0
- foldyard-0.0.1/src/foldyard/plugins/gcp_metadata/README.md +104 -0
- foldyard-0.0.1/src/foldyard/plugins/gcp_metadata/__init__.py +11 -0
- foldyard-0.0.1/src/foldyard/plugins/gcp_metadata/minter.py +254 -0
- foldyard-0.0.1/src/foldyard/plugins/gcp_metadata/server.py +260 -0
- foldyard-0.0.1/src/foldyard/plugins/gh_cli_token.py +53 -0
- foldyard-0.0.1/src/foldyard/plugins/github.py +482 -0
- foldyard-0.0.1/src/foldyard/plugins/github_app_token.py +288 -0
- foldyard-0.0.1/src/foldyard/plugins/inject.py +162 -0
- foldyard-0.0.1/src/foldyard/plugins/llm.py +65 -0
- foldyard-0.0.1/src/foldyard/plugins/proxy.py +625 -0
- foldyard-0.0.1/src/foldyard/plugins/static_token.py +56 -0
- foldyard-0.0.1/src/foldyard/plugins/vscode.py +25 -0
- foldyard-0.0.1/src/foldyard/ports.py +85 -0
- foldyard-0.0.1/src/foldyard/preflight.py +166 -0
- foldyard-0.0.1/src/foldyard/reconcile.py +276 -0
- foldyard-0.0.1/src/foldyard/skills.py +76 -0
- foldyard-0.0.1/src/foldyard/stack.py +1465 -0
- foldyard-0.0.1/src/foldyard/state_view.py +38 -0
- foldyard-0.0.1/src/foldyard/supervisor.py +1208 -0
- foldyard-0.0.1/src/foldyard/term.py +127 -0
- foldyard-0.0.1/src/foldyard/transcripts.py +400 -0
- foldyard-0.0.1/src/foldyard/tui.py +1983 -0
- foldyard-0.0.1/src/foldyard/verify.py +279 -0
- foldyard-0.0.1/src/foldyard/vscode.py +601 -0
- foldyard-0.0.1/src/foldyard/worktree.py +512 -0
- foldyard-0.0.1/tests/conftest.py +232 -0
- foldyard-0.0.1/tests/pbt.py +169 -0
- foldyard-0.0.1/tests/test_agent_guide.py +187 -0
- foldyard-0.0.1/tests/test_allowlist.py +343 -0
- foldyard-0.0.1/tests/test_box.py +988 -0
- foldyard-0.0.1/tests/test_browser.py +58 -0
- foldyard-0.0.1/tests/test_capture_box_e2e.py +571 -0
- foldyard-0.0.1/tests/test_cli.py +210 -0
- foldyard-0.0.1/tests/test_codex_chatgpt_token.py +136 -0
- foldyard-0.0.1/tests/test_config.py +798 -0
- foldyard-0.0.1/tests/test_config_pin.py +936 -0
- foldyard-0.0.1/tests/test_devmode.py +1009 -0
- foldyard-0.0.1/tests/test_docs.py +82 -0
- foldyard-0.0.1/tests/test_dual_home.py +43 -0
- foldyard-0.0.1/tests/test_e2e.py +223 -0
- foldyard-0.0.1/tests/test_example.py +127 -0
- foldyard-0.0.1/tests/test_example_lima_wall.py +87 -0
- foldyard-0.0.1/tests/test_exposure.py +307 -0
- foldyard-0.0.1/tests/test_fakecred.py +91 -0
- foldyard-0.0.1/tests/test_gcp_metadata_box_e2e.py +326 -0
- foldyard-0.0.1/tests/test_git_shim.py +470 -0
- foldyard-0.0.1/tests/test_githeal.py +131 -0
- foldyard-0.0.1/tests/test_github_minters.py +388 -0
- foldyard-0.0.1/tests/test_init.py +272 -0
- foldyard-0.0.1/tests/test_keyless.py +359 -0
- foldyard-0.0.1/tests/test_machine.py +698 -0
- foldyard-0.0.1/tests/test_machine_backend.py +416 -0
- foldyard-0.0.1/tests/test_minter.py +412 -0
- foldyard-0.0.1/tests/test_plugins.py +2413 -0
- foldyard-0.0.1/tests/test_ports.py +116 -0
- foldyard-0.0.1/tests/test_preflight.py +266 -0
- foldyard-0.0.1/tests/test_properties.py +261 -0
- foldyard-0.0.1/tests/test_proxy_box_e2e.py +409 -0
- foldyard-0.0.1/tests/test_proxy_e2e.py +292 -0
- foldyard-0.0.1/tests/test_proxy_inject.py +884 -0
- foldyard-0.0.1/tests/test_reconcile_model.py +473 -0
- foldyard-0.0.1/tests/test_reconcile_scenarios.py +205 -0
- foldyard-0.0.1/tests/test_skills.py +47 -0
- foldyard-0.0.1/tests/test_stack.py +1446 -0
- foldyard-0.0.1/tests/test_state_view.py +82 -0
- foldyard-0.0.1/tests/test_supervisor.py +1271 -0
- foldyard-0.0.1/tests/test_term.py +116 -0
- foldyard-0.0.1/tests/test_transcripts.py +456 -0
- foldyard-0.0.1/tests/test_tui.py +1856 -0
- foldyard-0.0.1/tests/test_verify.py +312 -0
- foldyard-0.0.1/tests/test_vscode.py +664 -0
- foldyard-0.0.1/tests/test_worktree.py +527 -0
- foldyard-0.0.1/uv.lock +2243 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
name: foldyard · live e2e
|
|
2
|
+
|
|
3
|
+
# foldyard's live container e2es. Two advisory tiers run here:
|
|
4
|
+
# - Docker-in-container topology for proxy/box e2es that need a sibling box to reach the test
|
|
5
|
+
# process by container IP.
|
|
6
|
+
# - Native Linux host topology with `[machine].backend = "native"` so CI exercises the real
|
|
7
|
+
# host-side `foldyard up` / worktree stack lifecycle without nested virtualization.
|
|
8
|
+
#
|
|
9
|
+
# The VM-backed `podman machine` lifecycle still needs a real VM provider (/dev/kvm or macOS
|
|
10
|
+
# virtualization) and stays in DEVELOPMENT.md, deliberately out of shared-runner CI.
|
|
11
|
+
#
|
|
12
|
+
# Split out from foldyard.yml so the fast lint/types/unit suite runs on every push (uniform with the
|
|
13
|
+
# other areas), while this heavy 25-min job is OPT-IN — mirroring platform-e2e. The workflow triggers
|
|
14
|
+
# on every push, but the job only runs on `main` (post-merge net), on a manual `workflow_dispatch`,
|
|
15
|
+
# or when a commit message carries `[run-e2e]` (the pre-merge opt-in you add when a branch is ready
|
|
16
|
+
# for the full check). On an ordinary feature-branch push the job is simply skipped — near-zero cost.
|
|
17
|
+
# Deliberately excluded from the CI gate (ci-gate.yml) as too heavy, so it's advisory and never
|
|
18
|
+
# blocks the required check.
|
|
19
|
+
on:
|
|
20
|
+
push: # all branches; the job-level `if` decides whether to run
|
|
21
|
+
workflow_dispatch: # allow manual runs
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: foldyard-e2e-${{ github.ref }}
|
|
25
|
+
cancel-in-progress: true
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
live-e2e:
|
|
29
|
+
name: live e2e (example stack + proxy + box, on Docker)
|
|
30
|
+
# Opt-in (mirrors platform-e2e): main, a manual dispatch, or a `[run-e2e]` commit message.
|
|
31
|
+
if: >-
|
|
32
|
+
github.ref == 'refs/heads/main'
|
|
33
|
+
|| contains(github.event.head_commit.message, '[run-e2e]')
|
|
34
|
+
|| github.event_name == 'workflow_dispatch'
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
timeout-minutes: 25
|
|
37
|
+
steps:
|
|
38
|
+
# Repo-only read: don't leave the GitHub token in .git/config. This job bind-mounts the
|
|
39
|
+
# workspace into a sibling container, so a persisted token would ride into it — and nothing
|
|
40
|
+
# here pushes. persist-credentials: false keeps the checkout but drops the credential.
|
|
41
|
+
- uses: actions/checkout@v6
|
|
42
|
+
with:
|
|
43
|
+
persist-credentials: false
|
|
44
|
+
|
|
45
|
+
# Warm the host Docker cache, in PARALLEL, so neither the `docker run` below (the uv image)
|
|
46
|
+
# nor the in-test pulls (the api base, the box image, postgres, alpine) wait on a serial pull.
|
|
47
|
+
# python:3.12-slim is the example api's FROM: without it warm, `foldyard up`'s buildkit does a
|
|
48
|
+
# live `load metadata for python:3.12-slim` against Docker Hub, which intermittently 502s and
|
|
49
|
+
# fails the build hard (no buildkit retry). Pre-pulling it (with retry) lets the in-test
|
|
50
|
+
# `docker compose build` resolve the base from the local cache instead of the registry.
|
|
51
|
+
- name: Pre-pull e2e images (parallel)
|
|
52
|
+
run: |
|
|
53
|
+
pull() { for i in 1 2 3 4 5; do docker pull "$1" && return 0; sleep $((i * 5)); done; return 1; }
|
|
54
|
+
for img in \
|
|
55
|
+
ghcr.io/astral-sh/uv:python3.12-bookworm \
|
|
56
|
+
quay.io/podman/stable:latest \
|
|
57
|
+
docker.io/library/python:3.12-slim \
|
|
58
|
+
docker.io/library/postgres:16-alpine \
|
|
59
|
+
docker.io/library/alpine:latest; do
|
|
60
|
+
pull "$img" &
|
|
61
|
+
done
|
|
62
|
+
wait
|
|
63
|
+
|
|
64
|
+
# The proxy/box live e2es need the test process to run INSIDE a container so a sibling box can
|
|
65
|
+
# reach the in-process proxy/emulator by container IP. Use docker CLI only (no podman), mount
|
|
66
|
+
# the host socket, and keep the repo at the same path so daemon-side bind mounts resolve.
|
|
67
|
+
# IN_DEVBOX=1 is the box signature `config.in_box()` keys on — a preset DOCKER_HOST alone is
|
|
68
|
+
# NOT one (Linux/WSL2 hosts export a socket too). It is what stands the launch gates down
|
|
69
|
+
# here exactly as in the dev box: preflight's fail-closed default-backend block, the
|
|
70
|
+
# config-adopt gate and `machine.ensure` all skip in-box, because there is no VM to manage
|
|
71
|
+
# and nothing host-side to adopt.
|
|
72
|
+
- name: live e2e inside a docker-CLI container
|
|
73
|
+
run: |
|
|
74
|
+
docker run --rm \
|
|
75
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
76
|
+
-v "$GITHUB_WORKSPACE:$GITHUB_WORKSPACE" \
|
|
77
|
+
-w "$GITHUB_WORKSPACE" \
|
|
78
|
+
-e FOLDYARD_E2E=1 \
|
|
79
|
+
-e IN_DEVBOX=1 \
|
|
80
|
+
-e DOCKER_HOST=unix:///var/run/docker.sock \
|
|
81
|
+
ghcr.io/astral-sh/uv:python3.12-bookworm \
|
|
82
|
+
bash -lc '
|
|
83
|
+
set -euo pipefail
|
|
84
|
+
apt-get update -qq
|
|
85
|
+
apt-get install -y -qq --no-install-recommends docker.io curl ca-certificates >/dev/null
|
|
86
|
+
# docker compose v2 plugin (the example-stack e2e shells `docker compose`); the static
|
|
87
|
+
# release binary, dropped into both plugin dirs the docker.io CLI searches.
|
|
88
|
+
for d in /usr/local/lib/docker/cli-plugins /usr/libexec/docker/cli-plugins; do
|
|
89
|
+
mkdir -p "$d"
|
|
90
|
+
curl -fsSL https://github.com/docker/compose/releases/download/v2.30.3/docker-compose-linux-x86_64 \
|
|
91
|
+
-o "$d/docker-compose"
|
|
92
|
+
chmod +x "$d/docker-compose"
|
|
93
|
+
done
|
|
94
|
+
docker compose version
|
|
95
|
+
uv run --group e2e pytest tests -k e2e -v
|
|
96
|
+
'
|
|
97
|
+
|
|
98
|
+
native-host-e2e:
|
|
99
|
+
name: native host e2e (example + worktree, on Podman)
|
|
100
|
+
# Same opt-in policy as the heavier Docker topology.
|
|
101
|
+
if: >-
|
|
102
|
+
github.ref == 'refs/heads/main'
|
|
103
|
+
|| contains(github.event.head_commit.message, '[run-e2e]')
|
|
104
|
+
|| github.event_name == 'workflow_dispatch'
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
timeout-minutes: 25
|
|
107
|
+
env:
|
|
108
|
+
FOLDYARD_E2E: "1"
|
|
109
|
+
FOLDYARD_ENGINE: podman
|
|
110
|
+
MACHINE_BACKEND: native
|
|
111
|
+
FOLDYARD_STATE_DIR: /tmp/foldyard-state
|
|
112
|
+
# NB: XDG_RUNTIME_DIR is set by the "Enable systemd --user session" step below to
|
|
113
|
+
# /run/user/$UID (NOT a /tmp path), so rootless podman can find the user systemd manager
|
|
114
|
+
# that schedules healthcheck timers. The native socket path derives from it
|
|
115
|
+
# (machine_backend.NativeBackend._socket_path → $XDG_RUNTIME_DIR/podman/podman.sock).
|
|
116
|
+
steps:
|
|
117
|
+
# Repo-only read (no push) — keep the token out of .git/config.
|
|
118
|
+
- uses: actions/checkout@v6
|
|
119
|
+
with:
|
|
120
|
+
persist-credentials: false
|
|
121
|
+
- uses: astral-sh/setup-uv@v8.2.0
|
|
122
|
+
|
|
123
|
+
# Rootless podman schedules a container's healthcheck INTERVALS via `systemd --user`
|
|
124
|
+
# transient timers. On a GH runner the `runner` user has no login session — so no user
|
|
125
|
+
# systemd manager and no /run/user/$UID — and the old custom XDG_RUNTIME_DIR=/tmp/... made
|
|
126
|
+
# it strictly worse (podman then can't find ANY user systemd). With no timer the example db's
|
|
127
|
+
# healthcheck never re-runs, its health stays `starting`, and compose's `depends_on:
|
|
128
|
+
# service_healthy` (the api → db) blocks `foldyard up` FOREVER — the job hung to its 25-min
|
|
129
|
+
# timeout. enable-linger starts user@$UID.service + creates /run/user/$UID; we point
|
|
130
|
+
# XDG_RUNTIME_DIR + the D-Bus session there so podman's `systemd-run --user` reaches it.
|
|
131
|
+
- name: Enable systemd --user session (rootless podman healthcheck timers)
|
|
132
|
+
run: |
|
|
133
|
+
set -euo pipefail
|
|
134
|
+
sudo loginctl enable-linger "$(id -un)"
|
|
135
|
+
uid="$(id -u)"
|
|
136
|
+
for _ in $(seq 1 30); do
|
|
137
|
+
test -S "/run/user/$uid/bus" && break
|
|
138
|
+
sleep 1
|
|
139
|
+
done
|
|
140
|
+
{
|
|
141
|
+
echo "XDG_RUNTIME_DIR=/run/user/$uid"
|
|
142
|
+
echo "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus"
|
|
143
|
+
} >> "$GITHUB_ENV"
|
|
144
|
+
# Sanity: the user manager is reachable (degraded is fine — we only need the timer path).
|
|
145
|
+
XDG_RUNTIME_DIR="/run/user/$uid" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" \
|
|
146
|
+
systemctl --user is-system-running || true
|
|
147
|
+
|
|
148
|
+
- name: Install native Podman backend
|
|
149
|
+
run: |
|
|
150
|
+
set -euo pipefail
|
|
151
|
+
sudo apt-get update -qq
|
|
152
|
+
sudo apt-get install -y -qq --no-install-recommends \
|
|
153
|
+
podman podman-compose uidmap slirp4netns fuse-overlayfs
|
|
154
|
+
mkdir -p "$XDG_RUNTIME_DIR/podman"
|
|
155
|
+
chmod 700 "$XDG_RUNTIME_DIR"
|
|
156
|
+
podman system service --time=0 "unix://$XDG_RUNTIME_DIR/podman/podman.sock" \
|
|
157
|
+
>"$RUNNER_TEMP/podman-service.log" 2>&1 &
|
|
158
|
+
echo "$!" > "$RUNNER_TEMP/podman-service.pid"
|
|
159
|
+
for _ in $(seq 1 20); do
|
|
160
|
+
if test -S "$XDG_RUNTIME_DIR/podman/podman.sock" && podman info >/dev/null 2>&1; then
|
|
161
|
+
break
|
|
162
|
+
fi
|
|
163
|
+
sleep 1
|
|
164
|
+
done
|
|
165
|
+
podman info
|
|
166
|
+
podman compose version || podman-compose --version
|
|
167
|
+
|
|
168
|
+
- name: Pre-pull native e2e images
|
|
169
|
+
run: |
|
|
170
|
+
set -euo pipefail
|
|
171
|
+
for img in \
|
|
172
|
+
docker.io/library/python:3.12-slim \
|
|
173
|
+
docker.io/library/postgres:16-alpine \
|
|
174
|
+
docker.io/library/alpine:latest; do
|
|
175
|
+
podman pull "$img" &
|
|
176
|
+
done
|
|
177
|
+
wait
|
|
178
|
+
|
|
179
|
+
# Prove the systemd --user timer path works BEFORE the e2e, so a broken healthcheck fails
|
|
180
|
+
# here in ~20s with a clear message instead of hanging `foldyard up`'s depends_on to the
|
|
181
|
+
# 25-min job timeout. A container with a trivial healthcheck must flip to `healthy`.
|
|
182
|
+
- name: Verify rootless healthchecks fire (fail fast, not a 25-min hang)
|
|
183
|
+
run: |
|
|
184
|
+
set -euo pipefail
|
|
185
|
+
cid=$(podman run -d --health-cmd 'true' --health-interval 2s --health-retries 3 \
|
|
186
|
+
docker.io/library/alpine:latest sleep 300)
|
|
187
|
+
state=""
|
|
188
|
+
for _ in $(seq 1 15); do
|
|
189
|
+
state=$(podman inspect -f '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo "")
|
|
190
|
+
[ "$state" = "healthy" ] && break
|
|
191
|
+
sleep 2
|
|
192
|
+
done
|
|
193
|
+
podman rm -f "$cid" >/dev/null 2>&1 || true
|
|
194
|
+
if [ "$state" != "healthy" ]; then
|
|
195
|
+
echo "✗ rootless podman healthchecks are NOT firing (last state: '${state:-none}')."
|
|
196
|
+
echo " The example db's health would never flip → foldyard up hangs on depends_on."
|
|
197
|
+
echo " Fix the systemd --user wiring (enable-linger / XDG_RUNTIME_DIR / DBUS above)."
|
|
198
|
+
exit 1
|
|
199
|
+
fi
|
|
200
|
+
echo "✓ rootless healthchecks fire — the example db will reach healthy."
|
|
201
|
+
|
|
202
|
+
- name: Native host e2e
|
|
203
|
+
run: uv run pytest tests/test_e2e.py -v
|
|
204
|
+
|
|
205
|
+
- name: Native backend diagnostics
|
|
206
|
+
if: always()
|
|
207
|
+
run: |
|
|
208
|
+
podman ps -a || true
|
|
209
|
+
cat "$RUNNER_TEMP/podman-service.log" || true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: foldyard
|
|
2
|
+
|
|
3
|
+
# foldyard's fast lint/types/unit suite (no container engine, no KVM). Runs on every push that
|
|
4
|
+
# touches foldyard, on ALL branches — uniform with the other areas' lint/test workflows
|
|
5
|
+
# (platform-test, infra-lint, contracts) so the CI gate (ci-gate.yml, PR-only) reliably finds this
|
|
6
|
+
# check across the PR's commits. The heavy live Docker e2es are split out into foldyard-e2e.yml,
|
|
7
|
+
# which stays PR + post-merge scoped.
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: foldyard-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
check:
|
|
17
|
+
name: lint + types + unit
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v6
|
|
22
|
+
- uses: extractions/setup-just@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v8.2.0
|
|
24
|
+
# ruff (lint + format-check) + pyright + ty + the unit/golden/TUI suite. No engine. typecheck
|
|
25
|
+
# installs the `e2e` group so the opt-in proxy/box e2e tests resolve cryptography/requests.
|
|
26
|
+
- run: just check
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Tag → build → publish to PyPI. Lives under foldyard/.github/ in the origin monorepo so that repo
|
|
4
|
+
# never runs it; in the standalone repo it is the .github/workflows/release.yml at the root.
|
|
5
|
+
#
|
|
6
|
+
# Trusted publishing: PyPI is told to trust THIS workflow in THIS repo (project settings →
|
|
7
|
+
# Publishing → add a GitHub publisher: owner twistco, repo foldyard, workflow release.yml,
|
|
8
|
+
# environment pypi). No API token is stored anywhere; `uv publish` exchanges the job's OIDC
|
|
9
|
+
# token for a short-lived upload token. Build and publish are separate jobs so the publish job
|
|
10
|
+
# holds the id-token permission and nothing else, and never runs project code.
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
tags: ["v*"]
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 10
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v6
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
- uses: astral-sh/setup-uv@v8.2.0
|
|
24
|
+
- name: Tag must match pyproject version
|
|
25
|
+
run: |
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
version="$(uv version --short)"
|
|
29
|
+
if [ "$tag" != "$version" ]; then
|
|
30
|
+
echo "✗ tag v$tag does not match [project].version = $version — bump pyproject first"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
- run: uv build
|
|
34
|
+
- uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
if-no-files-found: error
|
|
39
|
+
|
|
40
|
+
publish:
|
|
41
|
+
needs: build
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
timeout-minutes: 10
|
|
44
|
+
environment: pypi
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write
|
|
47
|
+
steps:
|
|
48
|
+
- uses: astral-sh/setup-uv@v8.2.0
|
|
49
|
+
- uses: actions/download-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
- run: uv publish
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# VM-visible scratch the box+proxy e2e mounts into a sibling box (cleaned up per run)
|
|
2
|
+
.e2e-tmp/
|
|
3
|
+
|
|
4
|
+
# hypothesis example database (property-based tests)
|
|
5
|
+
.hypothesis/
|
|
6
|
+
|
|
7
|
+
# uv-managed virtualenv + lockfile churn / build artefacts
|
|
8
|
+
.venv/
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.pyc
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
|
|
16
|
+
# Covered by the monorepo root .gitignore today; listed here so the standalone repo has them too.
|
|
17
|
+
.DS_Store
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
# Per-developer overrides (deep-merged over foldyard.toml — documented by foldyard.local.toml.example)
|
|
20
|
+
foldyard.local.toml
|
|
21
|
+
.claude/settings.local.*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes, per release. Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
4
|
+
versions follow [SemVer](https://semver.org/) once past `1.0`. Before that, minor versions may
|
|
5
|
+
break config or CLI shape, and say so here.
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
8
|
+
|
|
9
|
+
This repository is a force-pushed snapshot of the foldyard tree in its origin monorepo until
|
|
10
|
+
`0.1.0` (see `CONTRIBUTING.md`).
|
|
11
|
+
|
|
12
|
+
## 0.1.0 — unreleased
|
|
13
|
+
|
|
14
|
+
- The first release intended for general use.
|
|
15
|
+
|
|
16
|
+
## 0.0.1 — 2026-09-06
|
|
17
|
+
|
|
18
|
+
First published release, and an **alpha**. The engine has run daily inside Twist's monorepo for
|
|
19
|
+
months, but this is its first life as a standalone package — cut early to establish the name on
|
|
20
|
+
PyPI and to exercise the release path before it matters. Expect the config and CLI shape to move
|
|
21
|
+
before `0.1.0`; see the Status section of `README.md` for what is and isn't validated (the host
|
|
22
|
+
side is exercised on macOS only).
|
|
23
|
+
|
|
24
|
+
- Extracted from Twist's monorepo as a squash-start ([ADR-0013](./docs/adrs/0013-in-repo-carve-out-until-extraction.md)).
|
|
25
|
+
- Consumption is PyPI-first ([ADR-0020](./docs/adrs/0020-post-extraction-consumption-model.md),
|
|
26
|
+
amended): `uv tool install foldyard`. Published by tag from the release workflow via PyPI
|
|
27
|
+
trusted publishing — no API token exists anywhere.
|
foldyard-0.0.1/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEVELOPMENT.md
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Contributing to foldyard
|
|
2
|
+
|
|
3
|
+
Thanks for looking. Two things to know before you start.
|
|
4
|
+
|
|
5
|
+
**Where development happens.** Until `0.1.0` this repository is a *snapshot*: foldyard is still
|
|
6
|
+
developed inside the monorepo it is being extracted from, and `main` here is re-created from it
|
|
7
|
+
(one squash commit, force-pushed) as that work lands. Issues are welcome and read; a pull
|
|
8
|
+
request will be ported by hand rather than merged, and its history will not survive. After
|
|
9
|
+
`0.1.0` this becomes the home and that caveat goes away — `CHANGELOG.md` says which side of the
|
|
10
|
+
line we are on.
|
|
11
|
+
|
|
12
|
+
**How to work on it.** [`DEVELOPMENT.md`](./DEVELOPMENT.md) is the guide: the module map, the
|
|
13
|
+
test tiers (what runs anywhere, what needs an engine, what only runs on a real host), the
|
|
14
|
+
conventions, and the don't-break list. The short version:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv tool install --force --editable ".[host]" # the `fy` on your PATH runs your checkout
|
|
18
|
+
just check # ruff + pyright + ty + the suite — what CI gates on
|
|
19
|
+
just test -k <pattern> # one area
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- Design decisions live in [`docs/adrs/`](./docs/adrs/). A change that reverses one needs a new
|
|
23
|
+
ADR that supersedes it, not an edit to the old one.
|
|
24
|
+
- New property-based tests are validated **red first** — mutate the code, watch hypothesis find
|
|
25
|
+
the counterexample, revert. Commit before you mutate.
|
|
26
|
+
- Write "host", not "Mac"; the host being macOS is a fact about today's users, not the design.
|
|
27
|
+
- Security-relevant findings go to the address in [`SECURITY.md`](./SECURITY.md), not an issue.
|
|
28
|
+
|
|
29
|
+
By contributing you agree that your contributions are licensed under the Apache License 2.0
|
|
30
|
+
that covers the project (`LICENSE`).
|