sandbox.yml 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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/landlock-run 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. - os: ubuntu-latest
  43. runner: bwrap
  44. - os: ubuntu-24.04
  45. runner: landlock
  46. - os: ubuntu-24.04-arm
  47. runner: landlock
  48. - os: macos-latest
  49. runner: seatbelt
  50. name: sandbox e2e (${{ matrix.runner }}, ${{ matrix.os }})
  51. runs-on: ${{ matrix.os }}
  52. timeout-minutes: 20
  53. steps:
  54. - uses: actions/checkout@v6
  55. - uses: pnpm/action-setup@v4
  56. - uses: actions/setup-node@v6
  57. with:
  58. node-version: 24
  59. - name: Install (immutable)
  60. run: pnpm install --frozen-lockfile
  61. # The bwrap rung needs bubblewrap on PATH and unprivileged user
  62. # namespaces. Ubuntu 24.04 gates the latter behind an AppArmor knob;
  63. # lift it best-effort — on images where the knob is absent the
  64. # functional probe (and the run-guard below) is the arbiter anyway.
  65. - name: Install bubblewrap (unrestrict userns)
  66. if: matrix.runner == 'bwrap'
  67. run: |
  68. sudo apt-get update -q
  69. sudo apt-get install -yq bubblewrap
  70. sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 \
  71. || echo "apparmor userns knob absent — the functional probe decides"
  72. - name: Build Landlock launcher for this architecture
  73. if: matrix.runner == 'landlock'
  74. run: |
  75. sudo apt-get update -q
  76. sudo apt-get install -yq musl-tools
  77. pnpm --dir native/landlock-run run build:ts
  78. pnpm --dir native/landlock-run run build:native
  79. # The unit suite runs on ubuntu in `checks`; this is the one darwin leg
  80. # in the workflow, so run it here too — the platform-dependent unit
  81. # expectations (Seatbelt path canonicalization: /tmp IS /private/tmp)
  82. # take their darwin branch only on this runner.
  83. - name: Unit tests (darwin parity)
  84. if: matrix.runner == 'seatbelt'
  85. run: pnpm run test
  86. - name: Sandbox e2e (real kernel confinement, world-verified)
  87. # NO_COLOR: vitest force-enables ANSI color under GITHUB_ACTIONS even
  88. # without a TTY, which would thread escape codes through the summary
  89. # line the run-guard greps.
  90. env:
  91. NO_COLOR: 1
  92. run: |
  93. set -u +e -o pipefail
  94. out=$(pnpm exec vitest run --config vitest.e2e.config.ts \
  95. packages/sandbox/sandbox-local/tests/${{ matrix.runner }}.e2e.ts \
  96. packages/shell/bash-sandbox/tests/${{ matrix.runner }}.e2e.ts 2>&1); status=$?
  97. echo "$out"
  98. [ "$status" -eq 0 ]
  99. # Both platform files must have RUN — a self-skip (runner missing on
  100. # the very platform that exists to prove it) is a failure, not a pass.
  101. echo "$out" | grep -qE 'Test Files[[:space:]]+2 passed \(2\)'
  102. # Publish-path rehearsal, Landlock legs only. Build the launcher on its
  103. # native architecture, then install the local native and harness tarballs
  104. # together so registry state cannot mask source/package drift.
  105. - name: Build packages for the pack rehearsal
  106. if: matrix.runner == 'landlock'
  107. run: pnpm run build:official
  108. - name: Packed-distribution e2e (pack → install → confine)
  109. if: matrix.runner == 'landlock'
  110. env:
  111. NO_COLOR: 1
  112. run: |
  113. set -u +e -o pipefail
  114. out=$(pnpm exec vitest run --config vitest.e2e.config.ts \
  115. packages/sandbox/sandbox-local/tests/packed-install.e2e.ts 2>&1); status=$?
  116. echo "$out"
  117. [ "$status" -eq 0 ]
  118. echo "$out" | grep -qE 'Test Files[[:space:]]+1 passed \(1\)'