Repository navigation
Proof: freeze source-bound Arb/MPFI BUILD → RUN boundary #514
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
109 commits
Select commit
Hold shift + click to select a range
a8c38b3
Proof: добавить точный Arb evaluator и входы сборки
lemone112 2ae2514
Proof: связать comparator с двумя сборками
lemone112 9fdce2d
Proof: закрепить диалект зависимостей на GNU C17
lemone112 178a72a
Proof: нормализовать время source snapshot
lemone112 5852638
Proof: связать snapshot policy с наблюдением
lemone112 592c74a
Provide private scratch for locked FLINT tests
lemone112 3cceea3
Run Arb gate on an ephemeral hosted VM
lemone112 acb8355
Clarify the diagnostic Arb boundary
lemone112 5137840
Proof: замкнуть диагностический Arb runtime
lemone112 49629d2
CI: вывести Arb gate из карантина
lemone112 aedb4e1
CI: закрепить новый путь Arb gate
lemone112 8fe2532
Proof: закрыть fail-open замечания ревью
lemone112 de1a7cf
Proof: закрыть финальный review gate
lemone112 73d5df5
Proof: замкнуть source-build-run единым receipt
lemone112 21e5093
Proof: повторно допустить archive bytes перед replay
lemone112 8f85735
Proof: закрыть замечания к source-bound receipt
lemone112 4242676
Proof: вынести общую execution-границу
lemone112 38fcd06
Proof: сделать execution-координаты неизменяемыми
lemone112 cd9e590
Add exact MPFI source admission
lemone112 8a63b23
Proof: уточнить MPFI source admission
lemone112 443d0a8
Docs: уточнить длину source-lock wire
lemone112 9f1f1a7
Docs: scope proof wire codecs
lemone112 f6c7dbf
Proof: вынести общую BUILD-границу
lemone112 2e415d3
Proof: изолировать CID cleanup и lifecycle
lemone112 f34fa37
Proof: укрепить transport boundary и receipt trust
lemone112 6600223
Proof: preserve stream ownership during cleanup
lemone112 1b3ffbf
Test: always reap hostile observer fixture
lemone112 08e0243
Test: register observer cleanup before fixture setup
lemone112 c010af9
Build: типизировать невалидный public input
lemone112 9e10df2
Proof: сохранить Arb binding и total probe
lemone112 f465413
Proof: totalize native cleanup failure
lemone112 ba538b0
Test: уточнить fixed-arity boundary
lemone112 f645d75
Proof: totalize native build observations
lemone112 284ee16
Proof: retain native cleanup evidence
lemone112 33dfded
Proof: зафиксировать BUILD binding на capability
lemone112 4308d03
Proof: вынести общий source materializer
lemone112 a7b668d
Test: restore hostile lock fixture cache
lemone112 7155024
Proof: seal source provenance identities
lemone112 8fd4562
Proof: запечатать MPFI source input
lemone112 173c4ef
Test: запечатать общий proof inventory
lemone112 4c78b4e
Test: закрепить MPFI input в обязательном proof gate
lemone112 e588d43
Test: уточнить MPFI fixture guard
lemone112 ec13060
Proof: замкнуть single-operation replay
lemone112 bd7ddba
Proof: убрать stale test scaffolding
lemone112 6cd4f1b
Test: bind policy mutations to constructor fields
lemone112 f23c627
Proof: seal V2 build identity review fixes
lemone112 e393a32
Merge remote-tracking branch 'origin/agent/build-transport' into agen…
lemone112 86ef5ce
Merge remote-tracking branch 'origin/agent/mpfi-source-materializatio…
lemone112 bd47c14
Proof: keep MPFI capability rejection explicit
lemone112 4d67528
Merge remote-tracking branch 'origin/agent/v5b2c1-arb-source-build' i…
lemone112 709c7d2
Merge remote-tracking branch 'origin/agent/v5b2c2-arb-receipt' into a…
lemone112 d1aee34
Merge latest proof executor contract
lemone112 78a174e
Merge remote-tracking branch 'origin/agent/mpfi-source' into agent/bu…
lemone112 e8ecc2f
Merge remote-tracking branch 'origin/agent/build-transport' into agen…
lemone112 ab635ac
Merge remote-tracking branch 'origin/agent/mpfi-source-materializatio…
lemone112 9e57c40
Proof: add independent MPFI evaluator closure
lemone112 2677ec5
Proof: close MPFI operation and ELF inspection gates
lemone112 dda5de6
Proof: bind MPFI runtime profile and link closure
lemone112 a462958
Proof: normalize ELF symbol names before MPFI admission
lemone112 3930e09
Proof: close GNU GMP ABI symbol aliases
lemone112 cf48614
Proof: close portable MPFI build gate
lemone112 03e7534
Proof: forbid opaque MPFI token pasting
lemone112 931c432
Test: expose Arb gate inventory helper
lemone112 5c2ffdc
Merge remote-tracking branch 'origin/agent/mpfi-build-closure' into a…
lemone112 e4495c8
Proof: fail closed on MPFI inventory inspection
lemone112 5bd475c
Proof: fail closed on compiler admission
lemone112 0795517
Proof: make MPFI build environment entrypoint structural
lemone112 b464b87
Proof: make MPFI dispatch source-bound and explicit
lemone112 89f0419
Proof: bind MPFI runtime profile to executor limits
lemone112 3383867
Proof: seal MPFI source-owned build input
lemone112 1d15c06
Proof: seal MPFI source-bound BUILD and RUN receipt
lemone112 263f998
CI: remove unused MPFI archive aliases
lemone112 63da01e
Docs: align source-bound receipt terminology
lemone112 227dffe
Test: guard MPFI build identity replay
lemone112 a614838
Fix: bound MPFI rejection diagnostics
lemone112 3b85358
Fix: keep MPFI rejection fallback bounded
lemone112 1f397aa
Test: harden MPFI diagnostic rendering
lemone112 9d7ac10
Fix: close MPFI source-bound review gaps
lemone112 6889964
Proof: centralize observer cgroup placement
lemone112 eff3d93
Proof: seal native coordinate placement
lemone112 730addb
Proof: tighten native coordinate contracts
lemone112 5048f38
Proof: rebase native coordinate gate
lemone112 404a357
Proof: seal read-free native coordinates
lemone112 86b9ea0
Test: разделить сценарии размещения observer
lemone112 6e922e5
Test: убрать лишнее право в cgroup fixture
lemone112 1dacb9d
Proof: keep one native coordinate boundary
lemone112 bd23a05
Docs: синхронизировать observation contract
lemone112 6e8bb64
CI: make workflow concurrency and shell gates exact
lemone112 9918984
Proof: make Arb build environment entrypoint structural
lemone112 9855693
Arb: bound evaluator transport and disable PR runner trigger
lemone112 20dcf0a
Test: rebind Arb identity goldens after hardening
lemone112 934892a
Proof: separate Arb recipe from evaluator identity
lemone112 78f319e
Arb: bind exact runtime profile to receipts
lemone112 8ed8ccf
CI: preserve mutation evidence queue
lemone112 30ea014
Docs: закрепить контракт наблюдения
lemone112 f54c2a7
Proof: закрыть terminal review без смены identities
lemone112 47dd7a1
Merge remote-tracking branch 'origin/main' into agent/mpfi-m2a-profile
lemone112 e943351
Merge remote-tracking branch 'origin/main' into agent/mpfi-m2a-profile
lemone112 4fc2884
Merge remote-tracking branch 'origin/main' into pr-514
90339fe
Proof: обобщить apparmor-userns прекондицию нативного гейта fail-closed
ae072d6
Proof: вернуть Docker boundary probe нативного гейта на канонический …
d06e699
Proof: починить нативную сборку Arb evaluator под GCC 15
d2e62ce
Proof: изолировать FLINT-заголовки как системные для диагностики eval…
309b147
Proof: admit each receipt lane into the delegated cgroup subtree
5a8a021
executor: admit static glibc startup syscalls in the seccomp allow-list
891808c
Proof: exclude defective MPFI upstream tests from the sealed build co…
ed9c43d
Proof: fail fast in the quick gate on native lane inventory drift
d29d630
Proof: advance Arb build characterization pins with the native gate g…
0e96b03
Build: advance WASM size budget pin to the merged point-representatio…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,305 @@ | ||
| name: Arb evaluator build and runtime | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - .github/workflows/arb.yml | ||
| - crates/labcolors-core/contracts/contextual-region-formula-v1.lcir | ||
| - proof/region/v1/** | ||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: arb-evaluator-build-runtime-${{ github.run_id }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| diagnostic-build-runtime: | ||
| name: two offline builds and runtime tests (no artifact) | ||
| # Docker is root-equivalent, so this label is provisioned only on a fresh | ||
| # one-job VM whose runner group is bound to this exact workflow revision. | ||
| runs-on: [self-hosted, Linux, X64, labcolors-ephemeral] | ||
| timeout-minutes: 360 | ||
| env: | ||
| PYTHONDONTWRITEBYTECODE: "1" | ||
| PYTHONHASHSEED: "0" | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: complete fast Arb contract with exact skip manifest | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| python3 proof/region/v1/arb/tests/gate.py | ||
| PYTHONOPTIMIZE=2 python3 proof/region/v1/arb/tests/gate.py | ||
|
|
||
| - name: complete fast MPFI source contract with exact inventory | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| python3 proof/region/v1/mpfi/tests/gate.py | ||
| PYTHONOPTIMIZE=2 python3 proof/region/v1/mpfi/tests/gate.py | ||
|
|
||
| - name: bind run-local native paths after the fast gate | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| scope="/sys/fs/cgroup/labcolors-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" | ||
| { | ||
| echo "LABCOLORS_CGROUP_SCOPE_V1=$scope" | ||
| echo "LABCOLORS_EXECUTOR_CGROUP_V1=$scope/proof" | ||
| } >> "$GITHUB_ENV" | ||
|
|
||
| - name: acquire and hash-check exact source archives | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| source_dir="$RUNNER_TEMP/arb-source-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" | ||
| install -d -m 0700 "$source_dir" | ||
| echo "LABCOLORS_ARB_SOURCE_DIR=$source_dir" >> "$GITHUB_ENV" | ||
| export PYTHONPATH="$GITHUB_WORKSPACE/proof/region/v1" | ||
| python3 - <<'PY' > "$source_dir/lock.tsv" | ||
| import provenance | ||
|
|
||
| for source in provenance.arb_source_lock_v1().sources: | ||
| print( | ||
| source.role.name, | ||
| source.archive_url, | ||
| source.archive_sha256.hex(), | ||
| source.archive_length, | ||
| sep="\t", | ||
| ) | ||
| PY | ||
| count=0 | ||
| while IFS=$'\t' read -r role url digest length; do | ||
| archive="$source_dir/${role}.archive" | ||
| curl --fail --location --silent --show-error \ | ||
| --connect-timeout 30 --max-time 600 --retry 3 --retry-all-errors \ | ||
| "$url" --output "$archive" | ||
| test "$(stat --format=%s "$archive")" = "$length" | ||
| echo "$digest $archive" | sha256sum --check --strict | ||
| case "$role" in | ||
| GMP) echo "LABCOLORS_GMP_ARCHIVE=$archive" >> "$GITHUB_ENV" ;; | ||
| MPFR) echo "LABCOLORS_MPFR_ARCHIVE=$archive" >> "$GITHUB_ENV" ;; | ||
| FLINT_ARB) echo "LABCOLORS_FLINT_ARCHIVE=$archive" >> "$GITHUB_ENV" ;; | ||
| *) exit 64 ;; | ||
| esac | ||
| count=$((count + 1)) | ||
| done < "$source_dir/lock.tsv" | ||
| test "$count" -eq 3 | ||
|
|
||
| - name: acquire and hash-check exact MPFI source closure | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| source_dir="${LABCOLORS_ARB_SOURCE_DIR:?}" | ||
| export PYTHONPATH="$GITHUB_WORKSPACE/proof/region/v1" | ||
| python3 - <<'PY' > "$source_dir/mpfi-lock.tsv" | ||
| import provenance | ||
|
|
||
| for source in provenance.mpfi_source_lock_v1().sources: | ||
| print( | ||
| source.role.name, | ||
| source.archive_url, | ||
| source.archive_sha256.hex(), | ||
| source.archive_length, | ||
| sep="\t", | ||
| ) | ||
| PY | ||
| count=0 | ||
| while IFS=$'\t' read -r role url digest length; do | ||
| case "$role" in | ||
| GMP) | ||
| archive="${LABCOLORS_GMP_ARCHIVE:?}" | ||
| ;; | ||
| MPFR) | ||
| archive="${LABCOLORS_MPFR_ARCHIVE:?}" | ||
| ;; | ||
| MPFI) | ||
| archive="$source_dir/MPFI.archive" | ||
| curl --fail --location --silent --show-error \ | ||
| --connect-timeout 30 --max-time 600 --retry 3 --retry-all-errors \ | ||
| "$url" --output "$archive" | ||
| echo "LABCOLORS_MPFI_ARCHIVE=$archive" >> "$GITHUB_ENV" | ||
| ;; | ||
| *) exit 64 ;; | ||
| esac | ||
| test "$(stat --format=%s "$archive")" = "$length" | ||
| echo "$digest $archive" | sha256sum --check --strict | ||
| count=$((count + 1)) | ||
| done < "$source_dir/mpfi-lock.tsv" | ||
| test "$count" -eq 3 | ||
|
|
||
| - name: acquire the exact pinned OCI manifest | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| export PYTHONPATH="$GITHUB_WORKSPACE/proof/region/v1" | ||
| docker_path="$(realpath "$(command -v docker)")" | ||
| test -f "$docker_path" | ||
| test ! -L "$docker_path" | ||
| image="$(python3 - <<'PY' | ||
| from arb import pipeline | ||
| print(pipeline.OCI_IMAGE_REFERENCE_V1) | ||
| PY | ||
| )" | ||
| "$docker_path" image inspect "$image" >/dev/null 2>&1 || | ||
| /usr/bin/timeout --signal=TERM --kill-after=30s 15m \ | ||
| "$docker_path" pull "$image" | ||
| echo "LABCOLORS_ARB_PIPELINE_DOCKER=$docker_path" >> "$GITHUB_ENV" | ||
|
|
||
| - name: acquire the exact pinned MPFI OCI manifest | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| export PYTHONPATH="$GITHUB_WORKSPACE/proof/region/v1" | ||
| docker_path="${LABCOLORS_ARB_PIPELINE_DOCKER:?}" | ||
| image="$(python3 - <<'PY' | ||
| from mpfi import build | ||
| print(build.MPFI_BUILD_IMAGE_REFERENCE_V1) | ||
| PY | ||
| )" | ||
| "$docker_path" image inspect "$image" >/dev/null 2>&1 || | ||
| /usr/bin/timeout --signal=TERM --kill-after=30s 15m \ | ||
| "$docker_path" pull "$image" | ||
| echo "LABCOLORS_MPFI_DOCKER=$docker_path" >> "$GITHUB_ENV" | ||
|
|
||
| - name: require the exact diagnostic Docker boundary | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| export PYTHONPATH="$GITHUB_WORKSPACE/proof/region/v1" | ||
| export LABCOLORS_ARB_PIPELINE_DOCKER | ||
| python3 - <<'PY' | ||
| import os | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| from arb import pipeline | ||
| from build import transport as build_transport | ||
|
|
||
| docker = build_transport.NativeDockerBuildBackendV1( | ||
| Path(os.environ["LABCOLORS_ARB_PIPELINE_DOCKER"]), | ||
| pipeline.ARB_BUILD_TRANSPORT_POLICY_V1, | ||
| ).probe() | ||
| print(repr(docker)) | ||
| if type(docker) is not build_transport.DockerSupportedV1: | ||
| sys.exit(78) | ||
| PY | ||
|
|
||
| - name: delegate one disposable cgroup subtree | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| apparmor_userns=/proc/sys/kernel/apparmor_restrict_unprivileged_userns | ||
| if [[ -f "$apparmor_userns" ]]; then | ||
| original_userns="$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)" | ||
| case "$original_userns" in | ||
| 0|1) ;; | ||
| *) exit 78 ;; | ||
| esac | ||
| echo "LABCOLORS_APPARMOR_USERNS_V1=$original_userns" >> "$GITHUB_ENV" | ||
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 | ||
| test "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)" = 0 | ||
| else | ||
| # The restriction sysctl exists only on kernels carrying the | ||
| # AppArmor userns mediation; its absence is proof that there is | ||
| # nothing to lift, and the cleanup restore stays a no-op. | ||
| echo "LABCOLORS_APPARMOR_USERNS_V1=" >> "$GITHUB_ENV" | ||
| fi | ||
| scope="$LABCOLORS_CGROUP_SCOPE_V1" | ||
| sudo mkdir "$scope" | ||
| sudo chown "$(id -u):$(id -g)" \ | ||
| "$scope" \ | ||
| "$scope/cgroup.procs" \ | ||
| "$scope/cgroup.threads" \ | ||
| "$scope/cgroup.subtree_control" | ||
| printf '+memory +pids' > "$scope/cgroup.subtree_control" | ||
| mkdir "$scope/tasks" "$scope/proof" | ||
| printf '+memory +pids' > "$scope/proof/cgroup.subtree_control" | ||
| printf '2' > "$scope/proof/pids.max" | ||
| mkdir "$scope/proof/observer" | ||
| grep --fixed-strings --quiet 'memory' "$scope/proof/cgroup.subtree_control" | ||
| grep --fixed-strings --quiet 'pids' "$scope/proof/cgroup.subtree_control" | ||
| test "$(cat "$scope/proof/pids.max")" = 2 | ||
|
|
||
| - name: one source-bound BUILD to RUN receipt and evaluator runtime | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| # The kernel admits a cgroup migration only with write access to the | ||
| # common ancestor of the source and destination groups; the runner | ||
| # cgroup is root-owned, so root admits this step into the owned | ||
| # subtree first and the controller's observer placement then stays a | ||
| # proven self-migration between delegated groups. | ||
| echo "$$" | sudo tee \ | ||
| "$LABCOLORS_CGROUP_SCOPE_V1/tasks/cgroup.procs" >/dev/null | ||
| exec python3 proof/region/v1/arb/tests/native_gate.py receipt | ||
|
|
||
| - name: one source-bound MPFI BUILD to RUN receipt and evaluator runtime | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| # Identical admission contract as the Arb receipt lane above. | ||
| echo "$$" | sudo tee \ | ||
| "$LABCOLORS_CGROUP_SCOPE_V1/tasks/cgroup.procs" >/dev/null | ||
| exec python3 proof/region/v1/mpfi/tests/native_gate.py receipt | ||
|
|
||
| - name: native containment under an atomic two-task subtree | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| echo "$$" | sudo tee \ | ||
| "$LABCOLORS_EXECUTOR_CGROUP_V1/observer/cgroup.procs" >/dev/null | ||
| exec python3 proof/region/v1/arb/tests/native_gate.py executor | ||
|
|
||
| # No upload step: the static binary is an ephemeral observation until a | ||
| # linker/member inventory plus notices/source/relink distribution gate exists. | ||
|
|
||
| - name: remove disposable inputs and cgroup | ||
| if: always() | ||
| shell: bash | ||
| run: | | ||
| set -uo pipefail | ||
| status=0 | ||
| record_failure() { | ||
| local code="$?" | ||
| if (( status == 0 )); then | ||
| status="$code" | ||
| fi | ||
| } | ||
| if [[ -n "${LABCOLORS_ARB_SOURCE_DIR:-}" ]]; then | ||
| rm -rf -- "$LABCOLORS_ARB_SOURCE_DIR" || record_failure | ||
| fi | ||
| if [[ -n "${LABCOLORS_CGROUP_SCOPE_V1:-}" && \ | ||
| -d "$LABCOLORS_CGROUP_SCOPE_V1" ]]; then | ||
| if [[ -f "$LABCOLORS_CGROUP_SCOPE_V1/cgroup.kill" ]]; then | ||
| echo 1 | sudo tee "$LABCOLORS_CGROUP_SCOPE_V1/cgroup.kill" \ | ||
| >/dev/null || record_failure | ||
| fi | ||
| if [[ -f "$LABCOLORS_CGROUP_SCOPE_V1/cgroup.events" ]]; then | ||
| for _ in {1..100}; do | ||
| grep --fixed-strings --quiet 'populated 0' \ | ||
| "$LABCOLORS_CGROUP_SCOPE_V1/cgroup.events" && break | ||
| sleep 0.01 | ||
| done | ||
| grep --fixed-strings --quiet 'populated 0' \ | ||
| "$LABCOLORS_CGROUP_SCOPE_V1/cgroup.events" || record_failure | ||
| fi | ||
| for child in proof/observer proof tasks; do | ||
| if [[ -d "$LABCOLORS_CGROUP_SCOPE_V1/$child" ]]; then | ||
| sudo rmdir "$LABCOLORS_CGROUP_SCOPE_V1/$child" || record_failure | ||
| fi | ||
| done | ||
| sudo rmdir "$LABCOLORS_CGROUP_SCOPE_V1" || record_failure | ||
| fi | ||
| if [[ -n "${LABCOLORS_APPARMOR_USERNS_V1:-}" ]]; then | ||
| sudo sysctl -w \ | ||
| "kernel.apparmor_restrict_unprivileged_userns=$LABCOLORS_APPARMOR_USERNS_V1" \ | ||
| >/dev/null || record_failure | ||
| fi | ||
| exit "$status" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.