sandbox.yml 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. # Sandbox CI: the keyless real-kernel confinement proofs. This master-only
  2. # reference stays outside the pull-request verdict; rationale lives in
  3. # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md.
  4. # A separate workflow from ci.yml because the axis is different — these jobs
  5. # fan out over OS×runner (kernel capabilities), not node versions. The Landlock
  6. # launcher is built from native/system on each Landlock leg and installed
  7. # from the same tarballs the main-repository release workflow publishes.
  8. name: Sandbox
  9. on:
  10. push:
  11. branches: [master]
  12. concurrency:
  13. group: ${{ github.workflow }}-${{ github.ref }}
  14. cancel-in-progress: true
  15. permissions:
  16. contents: read
  17. env:
  18. # CI runs must never report to the production telemetry endpoint baked
  19. # into apps/cli/cordis.yml (AppCLIEntry disables the row when set).
  20. DSH_TELEMETRY_DISABLED: '1'
  21. jobs:
  22. # Keyless real-kernel sandbox proofs (sandbox Agent Note § Testing): each ladder
  23. # rung is only provable on a host where it enforces, so this job fans out
  24. # an OS×runner matrix — bwrap and Landlock on Linux (separate legs: the
  25. # Landlock files force the bwrap rung off, so each leg proves exactly one
  26. # rung; Landlock twice, once per architecture, each confining through the
  27. # locally built launcher), Seatbelt on macOS (sandbox-exec ships with
  28. # the OS). One node
  29. # version only: kernel confinement does not vary by node, and ci.yml's
  30. # node matrix already covers the node axis.
  31. #
  32. # The e2e files self-skip where their runner is absent, so a leg that lost
  33. # its runner (no bwrap, kernel without Landlock, macOS without
  34. # sandbox-exec) would otherwise pass as a false green — the same trap
  35. # e2e.yml's key preflight guards against. Each leg therefore asserts BOTH
  36. # its platform files actually ran: `Test Files 2 passed (2)`, no skips.
  37. sandbox-e2e:
  38. strategy:
  39. fail-fast: false
  40. matrix:
  41. include:
  42. # Only the bwrap leg has a Blacksmith equivalent: the earlier
  43. # migration dispatch runs measured that Blacksmith's Linux images do
  44. # not enforce Landlock (the run-guard would turn the leg red) and
  45. # its macOS image is unverified, so those legs stay on GitHub's
  46. # images under every failover value — see
  47. # .agents/notes/implemented/process/2026-09-09-blacksmith-failover-leg.md.
  48. - os: ubuntu-latest
  49. runner: bwrap
  50. - os: ubuntu-24.04
  51. runner: landlock
  52. - os: ubuntu-24.04-arm
  53. runner: landlock
  54. - os: macos-latest
  55. runner: seatbelt
  56. name: sandbox e2e (${{ matrix.runner }}, ${{ matrix.os }})
  57. runs-on: >-
  58. ${{ matrix.runner == 'bwrap' && vars.DSH_CI_FAILOVER_LINUX == 'blacksmith'
  59. && 'blacksmith-4vcpu-ubuntu-2404'
  60. || matrix.os }}
  61. timeout-minutes: 20
  62. steps:
  63. - uses: actions/checkout@v6
  64. - uses: pnpm/action-setup@v4
  65. - uses: actions/setup-node@v6
  66. with:
  67. node-version: 24
  68. - name: Install (immutable)
  69. run: pnpm install --frozen-lockfile
  70. # The bwrap rung needs bubblewrap on PATH and unprivileged user
  71. # namespaces. Ubuntu 24.04 gates the latter behind an AppArmor knob;
  72. # lift it best-effort — on images where the knob is absent the
  73. # functional probe (and the run-guard below) is the arbiter anyway.
  74. - name: Install bubblewrap (unrestrict userns)
  75. if: matrix.runner == 'bwrap'
  76. run: |
  77. sudo apt-get update -q
  78. sudo apt-get install -yq bubblewrap
  79. sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 \
  80. || echo "apparmor userns knob absent — the functional probe decides"
  81. - name: Build Landlock launcher for this architecture
  82. if: matrix.runner == 'landlock'
  83. run: |
  84. sudo apt-get update -q
  85. sudo apt-get install -yq musl-tools
  86. pnpm --dir native/system run build:ts
  87. pnpm --dir native/system run build:native
  88. # The unit suite runs on ubuntu in `checks`; this is the one darwin leg
  89. # in the workflow, so run it here too — the platform-dependent unit
  90. # expectations (Seatbelt path canonicalization: /tmp IS /private/tmp)
  91. # take their darwin branch only on this runner.
  92. - name: Unit tests (darwin parity)
  93. if: matrix.runner == 'seatbelt'
  94. run: pnpm run test
  95. - name: Sandbox e2e (real kernel confinement, world-verified)
  96. # NO_COLOR: vitest force-enables ANSI color under GITHUB_ACTIONS even
  97. # without a TTY, which would thread escape codes through the summary
  98. # line the run-guard greps.
  99. env:
  100. NO_COLOR: 1
  101. run: |
  102. set -u +e -o pipefail
  103. out=$(pnpm exec vitest run --config vitest.e2e.config.ts \
  104. packages/sandbox/sandbox-local/tests/${{ matrix.runner }}.e2e.ts \
  105. packages/shell/bash-sandbox/tests/${{ matrix.runner }}.e2e.ts 2>&1); status=$?
  106. echo "$out"
  107. [ "$status" -eq 0 ]
  108. # Both platform files must have RUN — a self-skip (runner missing on
  109. # the very platform that exists to prove it) is a failure, not a pass.
  110. echo "$out" | grep -qE 'Test Files[[:space:]]+2 passed \(2\)'
  111. # Publish-path rehearsal, Landlock legs only. Build the launcher on its
  112. # native architecture, then install the local native and harness tarballs
  113. # together so registry state cannot mask source/package drift.
  114. - name: Build packages for the pack rehearsal
  115. if: matrix.runner == 'landlock'
  116. run: pnpm run build:official
  117. - name: Packed-distribution e2e (pack → install → confine)
  118. if: matrix.runner == 'landlock'
  119. env:
  120. NO_COLOR: 1
  121. run: |
  122. set -u +e -o pipefail
  123. out=$(pnpm exec vitest run --config vitest.e2e.config.ts \
  124. packages/sandbox/sandbox-local/tests/packed-install.e2e.ts 2>&1); status=$?
  125. echo "$out"
  126. [ "$status" -eq 0 ]
  127. echo "$out" | grep -qE 'Test Files[[:space:]]+1 passed \(1\)'