aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:50:28 +0100
committereunhhu <[email protected]>2026-09-24 17:50:28 +0100
commitc63d70d535be7cf4b8c0a5739a7e623e51d2ebf0 (patch)
tree226611da0028e3bcd6f17f8615504ec32cb4a303
parent4525124c01cc4c4ee335b0a430354a235a6539e0 (diff)
downloadpure-c63d70d535be7cf4b8c0a5739a7e623e51d2ebf0.tar.gz
pure-c63d70d535be7cf4b8c0a5739a7e623e51d2ebf0.zip
Verify paged document app under 4 KiB AArch64 kernel
-rw-r--r--.github/workflows/native-linux-4k-page-store.yml4
-rw-r--r--.github/workflows/native-macos-arm64.yml4
-rw-r--r--CHECKPOINT.md4
-rw-r--r--README.md9
-rw-r--r--docs/research/071-logical-page-portability.md3
-rw-r--r--docs/research/072-qemu-4k-page-store.md30
-rw-r--r--prototypes/connections/README.md5
-rw-r--r--prototypes/connections/SOURCE_PREVIEW.md3
-rw-r--r--prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json23
-rw-r--r--prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json23
-rw-r--r--prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gzbin0 -> 250 bytes
-rw-r--r--prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gzbin0 -> 338483 bytes
-rw-r--r--prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json15
-rw-r--r--prototypes/connections/test-paged-store.c4
-rw-r--r--research/experiments/007-page-size-compatibility/guest.c63
-rw-r--r--research/experiments/007-page-size-compatibility/run_qemu_4k.py133
16 files changed, 316 insertions, 7 deletions
diff --git a/.github/workflows/native-linux-4k-page-store.yml b/.github/workflows/native-linux-4k-page-store.yml
index c559e46..e5ebe8a 100644
--- a/.github/workflows/native-linux-4k-page-store.yml
+++ b/.github/workflows/native-linux-4k-page-store.yml
@@ -11,6 +11,10 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
+ - uses: actions/setup-node@v7
+ with:
+ node-version: '24'
+ package-manager-cache: false
- name: Require a real 4 KiB OS page
run: test "$(getconf PAGESIZE)" = 4096
- name: Exercise the selected 16 KiB logical-page store
diff --git a/.github/workflows/native-macos-arm64.yml b/.github/workflows/native-macos-arm64.yml
index c3614be..f80491f 100644
--- a/.github/workflows/native-macos-arm64.yml
+++ b/.github/workflows/native-macos-arm64.yml
@@ -11,6 +11,10 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
+ - uses: actions/setup-node@v7
+ with:
+ node-version: '24'
+ package-manager-cache: false
- name: Confirm native Apple Silicon runner
run: |
test "$(uname -s)" = Darwin
diff --git a/CHECKPOINT.md b/CHECKPOINT.md
index a5692c9..86991cf 100644
--- a/CHECKPOINT.md
+++ b/CHECKPOINT.md
@@ -231,6 +231,10 @@ TUI의 Ctrl+S부터 저장 완료 표시까지 확인했다. 1 MiB 마지막 1 B
나누면 초기화를 허용하도록 좁게 바꿨다. 현재 16 KiB Linux의 실제 문서·
TUI·형식 내보내기는 통과했다. 4 KiB Linux와 macOS의 CI는 작성만 했고
실행 결과는 아직 없다.
+ [072](docs/research/072-qemu-4k-page-store.md)에서는 실제 4 KiB AArch64
+ Linux 커널을 QEMU에서 부팅해 선택형 저장소 단언과 1 MiB 고정 정의 CLI의
+ 생성·수정·충돌을 통과했다. tmpfs 게스트이므로 물리 디스크 내구성·지연이나
+ macOS 결과까지 증명하지 않는다.
3. **실제 네이티브 앱과 통신.** 작성·편집·저장·복원·충돌 처리를 연결하고 자체 통신 형식과
검증된 보안 전송을 붙인다. 외부 호환 입력·출력도 유지한다.
4. **규모·실패·사용성 확인과 공개 자료.** 큰 작업물·느린 소비자·혼합 작업에서 구조가
diff --git a/README.md b/README.md
index 6813d25..75698e6 100644
--- a/README.md
+++ b/README.md
@@ -61,10 +61,11 @@ source TUI adds 4,958 B linked `text`; its [CLI+TUI archive](prototypes/connecti
is 57,788 B compressed and passed a relocated app check. Large documents use
an incompatible on-disk page format, so this remains an opt-in Linux storage
research profile. Its 16 KiB logical pages now admit OS page sizes that divide
-16 KiB, but only the 16 KiB host has passed the real storage suite so far;
-the default core and storage path are unchanged. The
-[alignment and verification boundary](docs/research/071-logical-page-portability.md)
-records the 32 B selected-code cost and the unrun 4 KiB/macOS checks.
+16 KiB. The 16 KiB host passed the full selected suite, and a
+[real 4 KiB QEMU guest](docs/research/072-qemu-4k-page-store.md) passed the
+page-store assertions plus a 1 MiB native CLI edit and stale-revision check.
+Physical-disk durability, Linux x86_64 CI and macOS remain unverified; the
+default core and storage path are unchanged.
The current execution profile has separate `core`, `fixed-editor`, `editor`, and
`server` build targets. `make all` produces the core and the parser-free CLI
diff --git a/docs/research/071-logical-page-portability.md b/docs/research/071-logical-page-portability.md
index a664432..d57acaa 100644
--- a/docs/research/071-logical-page-portability.md
+++ b/docs/research/071-logical-page-portability.md
@@ -34,3 +34,6 @@ Linux AArch64에서 선택형 고정 CLI/HTTP의 링크 `text`는 각각
빌드·매핑·실제 문서 수정이 통과하기 전에는 이 변경을 검증된 플랫폼 지원으로
표기하지 않는다. 현재 저장소는 선택형이고, 큰 문서의 온디스크 형식은
기본 저장소와 호환되지 않아 [오프라인 내보내기](062-paged-store-rollback.md)가 필요하다.
+
+후속 [072](072-qemu-4k-page-store.md)에서 실제 4 KiB AArch64 게스트의
+저장소·1 MiB CLI 작업을 확인했다. 위 미실행 상태는 이 단계 당시의 기록이다.
diff --git a/docs/research/072-qemu-4k-page-store.md b/docs/research/072-qemu-4k-page-store.md
new file mode 100644
index 0000000..09b5548
--- /dev/null
+++ b/docs/research/072-qemu-4k-page-store.md
@@ -0,0 +1,30 @@
+# 072 — 실제 4 KiB 커널에서 페이지 저장소와 앱 실행
+
+2026-09-24. [071](071-logical-page-portability.md)의 정렬 추론을 실제
+OS 페이지 크기에서 검증했다. 16 KiB AArch64 호스트 위의 QEMU 10.0.13에서
+`CONFIG_ARM64_4K_PAGES=y`인 Linux 6.18.39 AArch64 커널을 부팅했다.
+게스트의 `sysconf(_SC_PAGESIZE)`는 **4,096 B**였다. 커널·설정·정적 게스트
+실행물의 SHA-256은 [실행 기록](../../prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json)에
+남겼다. [압축된 원시 콘솔 로그](../../prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz)는
+결과와 정상 전원 종료를 보여준다.
+
+게스트에서 기존 페이지 저장소 검사는 스냅샷, 범위 매핑, 충돌, 고장 주입,
+재구성, 인라인/페이지 전환과 128회 혼합 수정을 통과했다. 이어 실제
+고정 정의 `editor.pure` CLI가 1 MiB 문서를 생성하고 전체 바이트를
+다시 읽어 확인했다. 마지막 1 B 교체 뒤 리비전 2와 전체 본문을 확인했고,
+리비전 1의 재요청은 충돌로 거절돼 본문이 유지됐다.
+[두 번째 독립 실행](../../prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json)에서도
+게스트 바이너리와 재현 가능한 initramfs의 SHA-256이 일치했다.
+[재현 도구](../../research/experiments/007-page-size-compatibility/run_qemu_4k.py)와
+[게스트 검사 코드](../../research/experiments/007-page-size-compatibility/guest.c)가
+빌드·부팅·마커·해시 검사를 수행한다.
+
+수정한 저장소 테스트를 포함한 [최신 앱 소스 묶음](../../prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz)은
+다른 디렉터리에서 빌드하고 기본 릴리스·실제 PTY·선택형 페이지 TUI를
+[다시 검사했다](../../prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json).
+QEMU 재현 코드는 연구 디렉터리에 있으며 이 작은 앱 소스 묶음에는 들어 있지 않다.
+
+이 게스트의 파일 시스템은 initramfs다. `fsync` 호출과 충돌 계약은
+실행됐지만 **물리 디스크 내구성이나 실제 장치 지연은 검증하지 않았다**.
+4 KiB x86_64 CI와 macOS arm64 CI도 아직 미실행이다. 기본 코어·기본 저장
+형식은 바뀌지 않았고, 페이지 저장소는 계속 선택형이다.
diff --git a/prototypes/connections/README.md b/prototypes/connections/README.md
index 67cccd5..7da7eab 100644
--- a/prototypes/connections/README.md
+++ b/prototypes/connections/README.md
@@ -146,8 +146,9 @@ python3 prototypes/connections/export_flat_store.py \
같은 `.pure` 문서 편집기를 선택형 페이지 저장소와 함께 터미널에서 사용하려면
새 저장 디렉터리를 지정한다. 논리 페이지는 16 KiB이며 OS 페이지가 이를
-나눌 때만 초기화한다. 현재 실제 검사는 Linux 16 KiB 환경에서만 통과했고,
-4 KiB Linux와 macOS는 CI 실행 결과가 아직 없다. CLI와 TUI를
+나눌 때만 초기화한다. Linux 16 KiB 호스트에서 전체 선택형 검사가 통과했고,
+4 KiB QEMU 게스트에서 저장소 단언과 1 MiB 고정 CLI 작업을 통과했다.
+Linux x86_64 CI와 macOS 실행 결과는 아직 없다. CLI와 TUI를
함께 담은 [선택형 묶음](results/2026-09-24-native-save/pure-document-hybrid-cli-tui-linux-aarch64.tar.gz)은
다음 빌드로 만들며, 기본 `make all`과 기본 저장 형식은 유지된다.
diff --git a/prototypes/connections/SOURCE_PREVIEW.md b/prototypes/connections/SOURCE_PREVIEW.md
index a4bbfc3..7e4179e 100644
--- a/prototypes/connections/SOURCE_PREVIEW.md
+++ b/prototypes/connections/SOURCE_PREVIEW.md
@@ -83,6 +83,9 @@ python3 prototypes/connections/package_native.py \
python3 prototypes/connections/check_package.py /tmp/pure-hybrid-tui.tar.gz
```
+The 4 KiB AArch64 guest has passed the page-store assertions and a 1 MiB
+fixed-CLI edit; this archive does not include that QEMU research harness.
+
Use a new store directory for this profile; large documents use `PUREP001`
and need the offline exporter above before the default store can read them.
diff --git a/prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json
new file mode 100644
index 0000000..5866d72
--- /dev/null
+++ b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json
@@ -0,0 +1,23 @@
+{
+ "passed": true,
+ "os_page_bytes": 4096,
+ "checks": [
+ "128 mixed and fault-injected page-store edits",
+ "real prepared CLI creates and rereads every byte of a 1 MiB document",
+ "one-byte last-position revision edit and stale-revision rejection"
+ ],
+ "guest_shutdown": "power down",
+ "qemu_exit_code": 0,
+ "host": {
+ "system": "Linux",
+ "machine": "aarch64",
+ "page_bytes": 16384
+ },
+ "kernel_sha256": "fc341c15f1df9727f8dff25dc5acbbd872cc58ccf5d035a47ea8f6cecb07c5d8",
+ "config_sha256": "eb5d806796ea79dc73f73cce9ca3146fbd54318f3a3b0cd5b70a500d488e6114",
+ "guest_binary_sha256": "b8570d4f48e8b9bc5c4244af76d1d0f8854ef9e39e4487eb0f3ca78d6fc98ae5",
+ "initramfs_sha256": "e318b16208503a8779c9ab88eaaf5f8b3e3af030d5f8c646f153c5ec1ed1140e",
+ "log_sha256": "a22ba804cac91ba76a1e5f018f41026be74cca10b17600b1ab301b3d3c8fdd88",
+ "compressed_log_sha256": "d766f0fbd4069c047d01b7bdab184ccdd7cb748061494bb1d714c60fbf484f68",
+ "qemu_version": "QEMU emulator version 10.0.13 (Debian 1:10.0.13+ds-0+deb13u1)"
+}
diff --git a/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json
new file mode 100644
index 0000000..ebb1c0b
--- /dev/null
+++ b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json
@@ -0,0 +1,23 @@
+{
+ "passed": true,
+ "os_page_bytes": 4096,
+ "checks": [
+ "128 mixed and fault-injected page-store edits",
+ "real prepared CLI creates and rereads every byte of a 1 MiB document",
+ "one-byte last-position revision edit and stale-revision rejection"
+ ],
+ "guest_shutdown": "power down",
+ "qemu_exit_code": 0,
+ "host": {
+ "system": "Linux",
+ "machine": "aarch64",
+ "page_bytes": 16384
+ },
+ "kernel_sha256": "fc341c15f1df9727f8dff25dc5acbbd872cc58ccf5d035a47ea8f6cecb07c5d8",
+ "config_sha256": "eb5d806796ea79dc73f73cce9ca3146fbd54318f3a3b0cd5b70a500d488e6114",
+ "guest_binary_sha256": "b8570d4f48e8b9bc5c4244af76d1d0f8854ef9e39e4487eb0f3ca78d6fc98ae5",
+ "initramfs_sha256": "e318b16208503a8779c9ab88eaaf5f8b3e3af030d5f8c646f153c5ec1ed1140e",
+ "log_sha256": "894c401f1e888dfe6be360b8903fc384008ac4751758ca9aa52d5f9ba0efefcf",
+ "compressed_log_sha256": "128e5d54880b7f5b2322f94282dfad8aa949d7646e5066cb70d86088cdfd1ea4",
+ "qemu_version": "QEMU emulator version 10.0.13 (Debian 1:10.0.13+ds-0+deb13u1)"
+}
diff --git a/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz
new file mode 100644
index 0000000..b01be34
--- /dev/null
+++ b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz
Binary files differ
diff --git a/prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz
new file mode 100644
index 0000000..10051cd
--- /dev/null
+++ b/prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz
Binary files differ
diff --git a/prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json b/prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json
new file mode 100644
index 0000000..83b4063
--- /dev/null
+++ b/prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json
@@ -0,0 +1,15 @@
+{
+ "archive_sha256": "8de4a88acc0ecb6dd28cd569eb2ad1f6a88867898d86754ae2779f9adf771227",
+ "archive_bytes": 338483,
+ "source_files": 173,
+ "checks": [
+ "every extracted source file matches the manifest",
+ "clean default build contains only the core, parser-free CLI and its build-time compiler",
+ "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass",
+ "source-loaded CLI and parser-free CLI share the real stored document",
+ "editing .pure changes the native source-loaded reply",
+ "clean selected page store runs the source-loaded TUI and relocatable CLI+TUI package",
+ "clean Linux release behavior, code, dependency and RSS gates pass",
+ "clean source-loaded terminal editor PTY scenarios pass"
+ ]
+}
diff --git a/prototypes/connections/test-paged-store.c b/prototypes/connections/test-paged-store.c
index aed0539..aa867c6 100644
--- a/prototypes/connections/test-paged-store.c
+++ b/prototypes/connections/test-paged-store.c
@@ -165,7 +165,8 @@ static void mixed_edits(const char *path) {
}
int main(void) {
- assert(sysconf(_SC_PAGESIZE) == PAGE);
+ long host_page = sysconf(_SC_PAGESIZE);
+ assert(host_page > 0 && PAGE % host_page == 0);
char base[] = "/tmp/pure-page-store-XXXXXX";
assert(mkdtemp(base));
char path[256];
@@ -267,4 +268,5 @@ int main(void) {
}
assert(closedir(directory) == 0 && rmdir(path) == 0 && rmdir(base) == 0);
puts("{\"hybrid_storage\":\"snapshot-patch-range-compaction-crash-conflict-uncertain-inline-transitions\",\"mixed_edits\":128}");
+ return 0;
}
diff --git a/research/experiments/007-page-size-compatibility/guest.c b/research/experiments/007-page-size-compatibility/guest.c
new file mode 100644
index 0000000..0599bc1
--- /dev/null
+++ b/research/experiments/007-page-size-compatibility/guest.c
@@ -0,0 +1,63 @@
+/* PID 1 in the 4 KiB AArch64 test initramfs. Keep the storage assertions and
+ * the real prepared document CLI in the same guest with no host file mapping. */
+#define main paged_store_test_main
+#include "../../../prototypes/connections/test-paged-store.c"
+#undef main
+#include <sys/reboot.h>
+
+int pure_edit_main(int argc, char **argv);
+
+static void check_native_app(void) {
+ const char *store = "/tmp/pure-4k-app";
+ const char *input = "/tmp/pure-4k-input";
+ const char *replacement = "/tmp/pure-4k-replacement";
+ FILE *file = fopen(input, "wb");
+ assert(file);
+ unsigned char block[4096];
+ memset(block, 'A', sizeof(block));
+ for (unsigned i = 0; i < 256; ++i)
+ assert(fwrite(block, 1, sizeof(block), file) == sizeof(block));
+ assert(fclose(file) == 0);
+
+ char *first[] = {"pure-edit-fixed", "--store", (char *)store, "edit",
+ "note", "0", "0", "0", (char *)input};
+ assert(pure_edit_main(9, first) == 0);
+ PureStoredView view;
+ assert(pure_store_get(store, "note", 1048576, &view) == PURE_OK);
+ assert(view.revision == 1 && view.body.size == 1048576);
+ for (size_t i = 0; i < view.body.size; ++i) assert(view.body.data[i] == 'A');
+ pure_store_release(&view);
+
+ file = fopen(replacement, "wb");
+ assert(file && fputc('Z', file) == 'Z' && fclose(file) == 0);
+ char *second[] = {"pure-edit-fixed", "--store", (char *)store, "edit",
+ "note", "1", "1048575", "1", (char *)replacement};
+ assert(pure_edit_main(9, second) == 0);
+ assert(pure_store_get(store, "note", 1048576, &view) == PURE_OK);
+ assert(view.revision == 2 && view.body.size == 1048576);
+ for (size_t i = 0; i < view.body.size - 1; ++i) assert(view.body.data[i] == 'A');
+ assert(view.body.data[view.body.size - 1] == 'Z');
+ pure_store_release(&view);
+
+ assert(pure_edit_main(9, second) == 3);
+ assert(pure_store_get(store, "note", 1048576, &view) == PURE_OK);
+ assert(view.revision == 2 && view.body.data[view.body.size - 1] == 'Z');
+ pure_store_release(&view);
+}
+
+int main(void) {
+ long os_page = sysconf(_SC_PAGESIZE);
+ printf("PURE_GUEST_OS_PAGE=%ld\n", os_page);
+ fflush(stdout);
+ if (os_page != 4096) return 2;
+ int result = paged_store_test_main();
+ printf("PURE_4K_STORE_RESULT=%d\n", result);
+ fflush(stdout);
+ if (!result) {
+ check_native_app();
+ puts("PURE_4K_APP_RESULT=0");
+ fflush(stdout);
+ reboot(RB_POWER_OFF);
+ }
+ return result;
+}
diff --git a/research/experiments/007-page-size-compatibility/run_qemu_4k.py b/research/experiments/007-page-size-compatibility/run_qemu_4k.py
new file mode 100644
index 0000000..2a5b36e
--- /dev/null
+++ b/research/experiments/007-page-size-compatibility/run_qemu_4k.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+"""Run the selected page store and the real document CLI under a 4 KiB kernel."""
+import argparse
+import gzip
+import hashlib
+import json
+import os
+from pathlib import Path
+import platform
+import subprocess
+import tempfile
+
+
+ROOT = Path(__file__).resolve().parents[3]
+CONNECTIONS = ROOT / 'prototypes/connections'
+GUEST = Path(__file__).with_name('guest.c')
+
+
+def sha256(path):
+ return hashlib.sha256(path.read_bytes()).hexdigest()
+
+
+def run(command, *, cwd=None, data=None, timeout=120):
+ result = subprocess.run(command, cwd=cwd, input=data, capture_output=True,
+ timeout=timeout)
+ if result.returncode:
+ raise RuntimeError(f'{command[0]} failed ({result.returncode}): '
+ f'{result.stdout[-1000:]!r} {result.stderr[-1000:]!r}')
+ return result
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--kernel', type=Path, required=True,
+ help='gzip-compressed or raw AArch64 4 KiB Linux kernel')
+ parser.add_argument('--config', type=Path, required=True,
+ help='configuration belonging to that kernel')
+ parser.add_argument('--output', type=Path, required=True)
+ parser.add_argument('--log', type=Path, required=True)
+ parser.add_argument('--cc', default='cc')
+ parser.add_argument('--qemu', default='qemu-system-aarch64')
+ args = parser.parse_args()
+ if args.log.suffix != '.gz':
+ parser.error('--log must be a .gz path')
+ if platform.system() != 'Linux' or platform.machine() not in ('aarch64', 'arm64'):
+ parser.error('this runner compiles an AArch64 guest on an AArch64 Linux host')
+ if 'CONFIG_ARM64_4K_PAGES=y' not in args.config.read_text().splitlines():
+ parser.error('the supplied kernel configuration is not a 4 KiB AArch64 build')
+ if args.output.exists() or args.log.exists():
+ parser.error('evidence output already exists')
+
+ with tempfile.TemporaryDirectory(prefix='pure-qemu-4k-') as temporary:
+ work = Path(temporary)
+ build = work / 'build'
+ run(['make', '-C', str(CONNECTIONS), f'BUILD={build}', 'fixed-editor'])
+ edit = work / 'edit.o'
+ run([args.cc, '-O2', '-std=c17', '-Wall', '-Wextra', '-Wpedantic', '-Werror',
+ '-DPURE_EMBEDDED_PROGRAM', '-Dmain=pure_edit_main', f'-I{build}',
+ '-c', str(CONNECTIONS / 'edit.c'), '-o', str(edit)])
+
+ rootfs = work / 'rootfs'
+ rootfs.mkdir()
+ for name in ('tmp', 'dev'):
+ (rootfs / name).mkdir()
+ guest_binary = rootfs / 'init'
+ run([args.cc, '-O2', '-static', '-std=c17', '-Wall', '-Wextra',
+ '-Wpedantic', '-Werror', '-DPURE_STORE_TEST', f'-I{CONNECTIONS}',
+ str(GUEST), str(edit), str(CONNECTIONS / 'store-paged.c'),
+ str(CONNECTIONS / 'store-port.c'), str(CONNECTIONS / 'evaluate.c'),
+ str(CONNECTIONS / 'runtime.c'), '-o', str(guest_binary)])
+ guest_sha = sha256(guest_binary)
+ for path in (rootfs, rootfs / 'tmp', rootfs / 'dev', guest_binary):
+ os.utime(path, (0, 0))
+ archive = work / 'initrd.cpio'
+ entries = b'.\0./tmp\0./dev\0./init\0'
+ archive.write_bytes(run(['cpio', '--null', '-o', '-H', 'newc', '--owner=0:0',
+ '--reproducible'],
+ cwd=rootfs, data=entries).stdout)
+ kernel_image = work / 'Image'
+ with args.kernel.open('rb') as source:
+ compressed_kernel = source.read(2) == b'\x1f\x8b'
+ if compressed_kernel:
+ with gzip.open(args.kernel, 'rb') as source, kernel_image.open('wb') as target:
+ while chunk := source.read(1024 * 1024):
+ target.write(chunk)
+ else:
+ kernel_image.write_bytes(args.kernel.read_bytes())
+
+ command = [args.qemu, '-machine', 'virt', '-cpu', 'cortex-a72',
+ '-smp', '1', '-m', '256M', '-nographic', '-no-reboot',
+ '-nic', 'none', '-kernel', str(kernel_image), '-initrd', str(archive),
+ '-append', 'console=ttyAMA0 loglevel=4 rdinit=/init panic=-1']
+ guest_run = subprocess.run(command, capture_output=True, timeout=90)
+ log_bytes = guest_run.stdout + guest_run.stderr
+ args.log.parent.mkdir(parents=True, exist_ok=True)
+ with args.log.open('xb') as destination:
+ with gzip.GzipFile(fileobj=destination, mode='wb', filename='',
+ mtime=0, compresslevel=9) as compressed:
+ compressed.write(log_bytes)
+ log = log_bytes.decode(errors='replace').replace('\r\n', '\n')
+ required = ('PURE_GUEST_OS_PAGE=4096', 'PURE_4K_STORE_RESULT=0',
+ 'PURE_4K_APP_RESULT=0', '"mixed_edits":128',
+ '{"revision":"1","reply_bytes":1048576}',
+ '{"revision":"2","reply_bytes":1}',
+ '{"error":"conflict","revision":"2"}',
+ 'reboot: Power down')
+ missing = [marker for marker in required if marker not in log]
+ if guest_run.returncode or missing or 'Kernel panic' in log:
+ raise AssertionError(f'guest exit {guest_run.returncode}, missing {missing}; log: {args.log}')
+
+ evidence = {
+ 'passed': True, 'os_page_bytes': 4096,
+ 'checks': ['128 mixed and fault-injected page-store edits',
+ 'real prepared CLI creates and rereads every byte of a 1 MiB document',
+ 'one-byte last-position revision edit and stale-revision rejection'],
+ 'guest_shutdown': 'power down', 'qemu_exit_code': guest_run.returncode,
+ 'host': {'system': platform.system(), 'machine': platform.machine(),
+ 'page_bytes': os.sysconf('SC_PAGESIZE')},
+ 'kernel_sha256': sha256(args.kernel), 'config_sha256': sha256(args.config),
+ 'guest_binary_sha256': guest_sha, 'initramfs_sha256': sha256(archive),
+ 'log_sha256': hashlib.sha256(log_bytes).hexdigest(),
+ 'compressed_log_sha256': sha256(args.log),
+ 'qemu_version': run([args.qemu, '--version']).stdout.decode().splitlines()[0],
+ }
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ with args.output.open('x') as destination:
+ json.dump(evidence, destination, indent=2)
+ destination.write('\n')
+ print(json.dumps(evidence, indent=2))
+
+
+if __name__ == '__main__':
+ main()