diff --git a/.github/workflows/genesis-process-group-host-probe.yml b/.github/workflows/genesis-process-group-host-probe.yml new file mode 100644 index 00000000..dbfa3089 --- /dev/null +++ b/.github/workflows/genesis-process-group-host-probe.yml @@ -0,0 +1,198 @@ +name: Genesis process-group host probe (diagnostic only) + +# One pinned diagnostic tag, before any product repair or full qualification. +# All normal owning delivery/authorization checks remain separately required. +on: + push: + tags: ['l2-genesis-group-probe-*', 'l2-genesis-git-probe-*', 'l2-genesis-git-inventory-*', 'l2-genesis-git-cold-*', 'l2-genesis-product-probe-*'] + +permissions: + contents: read + +jobs: + linux-controller-probe: + name: Targeted installed controller coverage on Linux + if: startsWith(github.ref, 'refs/tags/l2-genesis-product-probe-') + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 + with: {fetch-depth: 0} + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: {python-version: '3.14'} + - name: Install exact committed candidate outside checkout + run: | + set -euo pipefail + python -m pip install coverage jsonschema==4.25.1 + python tools/qualification/build_platform_wheel.py --source-root . --version 2.3.124 --wheel-directory "$RUNNER_TEMP/ep-controller-wheel" --install-python "$(command -v python)" + python - <<'PY' + from pathlib import Path + import hashlib,json,os,zipfile,engineering_platform + wheel=Path(os.environ['RUNNER_TEMP'])/'ep-controller-wheel/engineering_platform-2.3.124-py3-none-any.whl' + package=Path(engineering_platform.__file__).resolve().parent + assert not package.is_relative_to(Path.cwd()) + with zipfile.ZipFile(wheel) as z: + files=[n for n in z.namelist() if n.startswith('engineering_platform/') and not n.endswith('/')] + assert all((package.parent/n).read_bytes()==z.read(n) for n in files) + Path('controller-installed-binding.json').write_text(json.dumps({'source':os.environ['GITHUB_SHA'],'wheel_sha256':hashlib.sha256(wheel.read_bytes()).hexdigest(),'payload_files':len(files),'qualification':'TARGETED_COMPONENT_ONLY_NOT_FULL_DOD'},indent=2)+'\n') + PY + - name: Targeted actual installed controller and filesystem regressions + run: | + set -euo pipefail + coverage run --branch --source=engineering_platform.genesis_git_runtime,engineering_platform.genesis_service,engineering_platform.genesis_worker,engineering_platform.platform_version -m unittest \ + tests.engineering.test_genesis_bootstrap tests.engineering.test_genesis_contract_and_cli \ + tests.engineering.test_genesis_git_runtime tests.engineering.test_genesis_git_recovery tests.engineering.test_genesis_git_controller \ + tests.engineering.test_platform_productization.PlatformProductizationTest.test_release_version_is_consistent_across_every_canonical_component \ + tests.engineering.test_execution_host.LocalAgentRunnerTest.test_engineering_platform_accepts_newer_compatible_runner \ + tests.engineering.test_execution_host.LocalAgentRunnerTest.test_default_runner_compatibility_accepts_the_repository_manifest \ + tests.engineering.test_execution_host.LocalAgentRunnerTest.test_current_storage_schema_is_admitted_for_retry_children \ + tests.engineering.test_execution_host.LocalAgentRunnerTest.test_engineering_platform_rejects_incompatible_platform_version \ + tests.engineering.test_execution_host.LocalAgentRunnerTest.test_engineering_platform_rejects_bootstrap_checkpoint_memory_and_report_mismatches \ + tests.engineering.test_execution_host.LocalAgentRunnerTest.test_engineering_platform_rejects_older_runner + coverage json -o controller-coverage.json + python - <<'PY' + import json + from pathlib import Path + report=json.loads(Path('controller-coverage.json').read_text()) + assert len(report['files'])==4 + for name,value in report['files'].items(): + summary=value['summary'] + line=100*summary['covered_lines']/summary['num_statements'] + print(name, 'line', line, 'combined', summary['percent_covered']) + assert line>80.2, name + # This targeted report does not replace the ordinary full-suite + # per-module combined branch/line 80.20% gate or any other CI check. + PY + - name: Preserve targeted original evidence even on failure + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: genesis-linux-controller-facts-${{ github.sha }} + path: | + controller-installed-binding.json + controller-coverage.json + .coverage + include-hidden-files: true + if-no-files-found: error + retention-days: 30 + macos-group-probe: + name: Original installed Genesis group facts on macOS-14 + runs-on: macos-14 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 + with: {fetch-depth: 0} + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: {python-version: '3.14'} + - name: Original qualified wheel and separate noneditable installation + if: "!startsWith(github.ref, 'refs/tags/l2-genesis-product-probe-')" + env: + BASE_SOURCE: 4d0c0461d7a5ec7fb7a50aa42f775cc4be09faa5 + run: | + set -euo pipefail + test "$RUNNER_ENVIRONMENT" = github-hosted + test ! -e /private/tmp/gpc1 + test ! -e /private/tmp/npc1 + git worktree add --detach "$RUNNER_TEMP/ep-original-source" "$BASE_SOURCE" + python tools/qualification/build_platform_wheel.py --source-root "$RUNNER_TEMP/ep-original-source" --version 2.3.123 --wheel-directory "$RUNNER_TEMP/ep-original-wheel" + python -m venv /private/tmp/gpc1 + /private/tmp/gpc1/bin/python -m pip install --no-deps "$RUNNER_TEMP/ep-original-wheel/engineering_platform-2.3.123-py3-none-any.whl" + /private/tmp/gpc1/bin/python -m pip install jsonschema==4.25.1 + npm install --prefix /private/tmp/npc1 --ignore-scripts --no-audit --no-fund @openai/codex@0.160.1 + python - <<'PY' + from pathlib import Path + import os + root=Path('/private/tmp/npc1') + found=[p for p in (root/'node_modules/@openai').rglob('codex') if '/vendor/' in str(p) and p.is_file() and os.access(p,os.X_OK)] + assert len(found)==1, 'one real pinned native executable required' + (root/'bin').mkdir() + (root/'bin/codex').symlink_to(found[0]) + PY + python - <<'PY' + from pathlib import Path + import hashlib,json,os + bundle=json.loads(Path('tests/engineering/fixtures/genesis_local/authority-bundle.json').read_text()) + matches=[value['utf8'].encode() for name,value in bundle['files'].items() if name.endswith('/validate.py')] + assert len(matches)==1 + assert hashlib.sha256(matches[0]).hexdigest()=='3ce4eca67c659d65ea2df70439d7f61bc8e4eb16b3d4b64b397d7fc3bdc16ae2' + path=Path(os.environ['RUNNER_TEMP'])/'original-validator.py' + path.write_bytes(matches[0]);path.chmod(0o600) + PY + - name: Current exact committed candidate and separate noneditable installation + if: startsWith(github.ref, 'refs/tags/l2-genesis-product-probe-') + run: | + set -euo pipefail + test "$RUNNER_ENVIRONMENT" = github-hosted + test ! -e /private/tmp/gpc1 + test ! -e /private/tmp/npc1 + python tools/qualification/build_platform_wheel.py --source-root . --version 2.3.124 --wheel-directory "$RUNNER_TEMP/ep-candidate-wheel" + python -m venv /private/tmp/gpc1 + /private/tmp/gpc1/bin/python -m pip install --no-deps "$RUNNER_TEMP/ep-candidate-wheel/engineering_platform-2.3.124-py3-none-any.whl" + /private/tmp/gpc1/bin/python -m pip install jsonschema==4.25.1 + npm install --prefix /private/tmp/npc1 --ignore-scripts --no-audit --no-fund @openai/codex@0.160.1 + python - <<'PY' + from pathlib import Path + import hashlib,json,os + root=Path('/private/tmp/npc1') + found=[p for p in (root/'node_modules/@openai').rglob('codex') if '/vendor/' in str(p) and p.is_file() and os.access(p,os.X_OK)] + assert len(found)==1 + (root/'bin').mkdir(); (root/'bin/codex').symlink_to(found[0]) + bundle=json.loads(Path('tests/engineering/fixtures/genesis_local/authority-bundle.json').read_text()) + matches=[value['utf8'].encode() for name,value in bundle['files'].items() if name.endswith('/validate.py')] + assert len(matches)==1 + assert hashlib.sha256(matches[0]).hexdigest()=='3ce4eca67c659d65ea2df70439d7f61bc8e4eb16b3d4b64b397d7fc3bdc16ae2' + path=Path(os.environ['RUNNER_TEMP'])/'original-validator.py' + path.write_bytes(matches[0]);path.chmod(0o600) + PY + - name: Small exact-candidate installed chain with unchanged group detector + if: startsWith(github.ref, 'refs/tags/l2-genesis-product-probe-') + run: | + set -euo pipefail + WHEEL="$RUNNER_TEMP/ep-candidate-wheel/engineering_platform-2.3.124-py3-none-any.whl" + SHA=$(shasum -a 256 "$WHEEL" | cut -d' ' -f1) + /private/tmp/gpc1/bin/python -I tools/qualification/genesis_process_group_host_probe.py \ + --python /private/tmp/gpc1/bin/python --native /private/tmp/npc1 \ + --bundle tests/engineering/fixtures/genesis_local/authority-bundle.json \ + --validator "$RUNNER_TEMP/original-validator.py" \ + --wheel "$WHEEL" --wheel-sha256 "$SHA" --source "$GITHUB_SHA" --root-prefix pgp \ + --output "$RUNNER_TEMP/ep-group-probe" --case before-register \ + --recipient tools/qualification/fixtures/genesis-process-group-evidence-recipient.pem + - name: Small original installed chain with unchanged group detector + if: startsWith(github.ref, 'refs/tags/l2-genesis-group-probe-') + run: | + set -euo pipefail + /private/tmp/gpc1/bin/python -I tools/qualification/genesis_process_group_host_probe.py \ + --python /private/tmp/gpc1/bin/python --native /private/tmp/npc1 \ + --bundle tests/engineering/fixtures/genesis_local/authority-bundle.json \ + --validator "$RUNNER_TEMP/original-validator.py" \ + --wheel "$RUNNER_TEMP/ep-original-wheel/engineering_platform-2.3.123-py3-none-any.whl" \ + --wheel-sha256 bc49e7a3b15c9021b16546a5ca96e5fbf280072d900d8d3dc173dee4b79d5b9e \ + --source 4d0c0461d7a5ec7fb7a50aa42f775cc4be09faa5 --root-prefix pgc \ + --output "$RUNNER_TEMP/ep-group-probe" --case before-register \ + --recipient tools/qualification/fixtures/genesis-process-group-evidence-recipient.pem + - name: Read-only causal native Git shim and selected-runtime comparison + if: startsWith(github.ref, 'refs/tags/l2-genesis-git-probe-') || startsWith(github.ref, 'refs/tags/l2-genesis-git-cold-') + run: | + set -euo pipefail + /private/tmp/gpc1/bin/python -I tools/qualification/genesis_git_runtime_probe.py \ + --order ${{ startsWith(github.ref, 'refs/tags/l2-genesis-git-cold-') && 'direct-first' || 'shim-first' }} \ + --python /private/tmp/gpc1/bin/python --native /private/tmp/npc1 \ + --output "$RUNNER_TEMP/ep-group-probe" \ + --recipient tools/qualification/fixtures/genesis-process-group-evidence-recipient.pem + - name: Read only selected Git ownership metadata without executing Git or native + if: startsWith(github.ref, 'refs/tags/l2-genesis-git-inventory-') + run: | + set -euo pipefail + /private/tmp/gpc1/bin/python -I tools/qualification/genesis_git_runtime_probe.py \ + --selection-inventory-only \ + --python /private/tmp/gpc1/bin/python --native /private/tmp/npc1 \ + --output "$RUNNER_TEMP/ep-group-probe" \ + --recipient tools/qualification/fixtures/genesis-process-group-evidence-recipient.pem + - name: Preserve whitelisted observations and encrypted original private evidence + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: genesis-macos-group-observations-${{ github.sha }} + path: ${{ runner.temp }}/ep-group-probe/public + if-no-files-found: error + retention-days: 30 diff --git a/docs/engineering/GENESIS_MACOS_GIT_RUNTIME_V1.md b/docs/engineering/GENESIS_MACOS_GIT_RUNTIME_V1.md new file mode 100644 index 00000000..31fd1fc1 --- /dev/null +++ b/docs/engineering/GENESIS_MACOS_GIT_RUNTIME_V1.md @@ -0,0 +1,82 @@ +# Genesis macOS selected physical Git + +Assignment `L2-GENESIS-MACOS-PROCESS-GROUP-CLOSURE-V1-20261019`; +JOIN `L2-L3-GENESIS-APPLY-HOST-QUALIFICATION-V1-20261019`. +Product allocation EP2.3.124, CENTRAL77, storage45, bootstrap contract1.0. +The earlier EP2.3.123 delivery and its evidence remain closed and unchanged. + +## Supported host mode + +`MACOS_OS_SELECTED_PHYSICAL_GIT_V1` uses the physical Git selected by the +existing root-owned `/var/db/xcode_select_link`. It accepts an executable under +`/Applications` or `/Library/Developer` only. Binary and ancestor ownership, +permissions, no-follow regular-file identity and bounded SHA256 reads are +checked. Root and the current trusted installation owner are supported; +non-root ancestors must not be group/world writable. Root-owned group-write +is accepted only for Darwin wheel/admin (GID0/80); other groups such as staff +do not acquire host execution authority. World-write always refuses. +No consumer supplies an +executable path, and there is no PATH or shim fallback for this macOS mode. + +The trusted installation-owner/system-administrator boundary is explicit. +Before/after hashes and inode metadata are drift checks; they are not an atomic +exec binding against an administrator or the same trusted owner replacing and +restoring executable bytes during execution. Unsupported or changed host +selection is an explicit error, not permission to silently replace a pin. + +The controller records the exact operation/host-bound selection in the private +signed attempt before native execution. PREFLIGHT actually runs fixed +`--version` inside the original native sandbox before target creation. Every +controller step checks the selection; every Git command checks its binary. +The worker does not read the selector through a new sandbox grant. Original +five-second Git and thirty-second step deadlines, sanitized environment, +finite filesystem capabilities, permit handshake and whole-group detector +remain in force. Linux retains its existing Git executable selection and rejects +a macOS-mode pin rather than treating it as authority for a fallback command. +The shared finite-log and original-empty-metadata recovery checks apply on both +supported platforms. + +## Original-operation recovery + +Explicit resume can add a new PREFLIGHT attempt to a signed legacy operation +which had completed native readiness before this mode existed. Earlier +attempts and completed-step history are retained; the completed prefix is not +duplicated. A failed current-mode pin remains binding and cannot be repinned. +Read/status never performs this migration or native work. COMPLETE remains the +original result. Current authority, source identity and original scopes are +required at their existing effect boundaries, including before recovery. + +An interrupted Git initialization can reuse only the originally granted Git +metadata inode, literally empty and still bound to the same target and exact +documents. The held execution descriptor is checked after the last runtime +hash and immediately before Git. Foreign or unproved nonempty metadata remains +preserved and refused; no delete, adoption, reset or replacement target is +authorized. Existing proved initialized Git follows its original guard. + +Private stdout/stderr and timeout bytes are retained within finite limits. +Overflow is failure, including successful Git with truncated stderr. Log I/O +failure cannot hide a primary nonzero exit, timeout or overflow; both errors +remain available. No public receipt exposes the private executable path, +credential or raw diagnostic details. + +## Evidence boundaries + +The original macOS14 shim chain reproduced a five-second GIT_INIT timeout with +live bash/xcodebuild members in the recorded group. The unchanged detector +correctly rejected that group. Later same-shim and reverse-order controls also +succeeded: a unique cache/warming cause is not established. Physical Git is an +explicit supported host mode, not a universal causal claim or a timeout waiver. + +New candidate installed evidence on local macOS27 and hosted macOS14 includes +actual native steps, committed documents, B8R, lost response, a real Server +crash/restart, nonmutating GET, separately authorized resume and replay. These +candidate receipts retain their own source/wheel pins. The legacy installed +migration uses a current controlled WRITE_FILES crash; it does not reconstruct +the unavailable historical hosted target or demonstrate its old Git failure. +Component fault injection and modeled PID-reuse controls remain labeled as +such. Candidate evidence is not exact-final-main proof or Forge R57 acceptance. + +Full owning CI, independent final reviews, protected source and separate +nonempty Finalization, NEW exact-final-main installed evidence, readback and +safe own cleanup are delivery gates. R57's writer/PR/timer/budgets remain +untouched until an actually qualified owning handoff; no automatic successor. diff --git a/package-lock.json b/package-lock.json index ebacbb9f..739a0f92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "engineering-platform-browser-validation", - "version": "2.3.123", + "version": "2.3.124", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "engineering-platform-browser-validation", - "version": "2.3.123", + "version": "2.3.124", "devDependencies": { "@openai/codex": "0.160.1", "@playwright/test": "1.64.0", diff --git a/package.json b/package.json index 79ec6c26..66f45dc2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "engineering-platform-browser-validation", "private": true, - "version": "2.3.123", + "version": "2.3.124", "scripts": { "test:engineering-dashboard": "PYTHONPATH=src python3 -m engineering_platform.dashboard_browser_validation", "test:engineering-dashboard-logic": "node --test tests/engineering/dashboard_status_store.test.mjs tests/engineering/ui_localization_contract.test.mjs tests/engineering/dashboard_translation.test.mjs tests/engineering/dashboard_js_coverage.test.mjs tests/engineering/dashboard_worker_logs.test.mjs tests/engineering/dashboard_log_read.test.mjs", diff --git a/pyproject.toml b/pyproject.toml index 52562ba9..d5087018 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "engineering-platform" -version = "2.3.123" +version = "2.3.124" description = "Local-first Engineering Platform execution operations runtime" readme = "README.md" requires-python = ">=3.14,<3.15" diff --git a/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json b/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json index b32d4c49..262bd16d 100644 --- a/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +++ b/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json @@ -3,7 +3,7 @@ "platform": { "id": "engineering-platform", "name": "Engineering Platform", - "version": "2.3.123", + "version": "2.3.124", "generation": 2, "documentation_namespace": "engineering-platform", "capability_registry_version": 1 diff --git a/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json b/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json index 99f7dddb..e3d75812 100644 --- a/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +++ b/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json @@ -1,15 +1,15 @@ { "bootstrap_contract": "2026.12", "checkpoint_format": 1, - "dashboard_version": "2.3.123", + "dashboard_version": "2.3.124", "handoff_protocol": 1, "memory_format": 2, "minimum_codex_cli": "0.146.0", "inbox_protocol": 1, - "platform_version": "2.3.123", + "platform_version": "2.3.124", "report_format": 2, - "runner_version": "2.3.123", + "runner_version": "2.3.124", "status_model": 1, "storage_schema": 45, - "watcher_version": "2.3.123" + "watcher_version": "2.3.124" } diff --git a/src/engineering_platform/genesis_git_runtime.py b/src/engineering_platform/genesis_git_runtime.py new file mode 100644 index 00000000..c558978d --- /dev/null +++ b/src/engineering_platform/genesis_git_runtime.py @@ -0,0 +1,107 @@ +"""Operation-bound physical Git for the trusted macOS installation owner. + +Apple's /usr/bin/git is a tool-selection shim which can start Xcode helpers. +This mode executes the existing OS-selected Git inside the original sandbox. +It is not protection against an administrator or the same owner replacing +installed executables during exec. No arbitrary consumer path or PATH fallback +is accepted. Selection drift is an error, never permission to replace a pin. +""" +from __future__ import annotations + +import os +from pathlib import Path +import stat +import sys +import time + +from .genesis_contract import BootstrapError, canonical +from .preproject_inspection import directory, selected_bytes, InspectionError + +SELECTION = Path('/var/db/xcode_select_link') +ROOTS = (Path('/Applications'), Path('/Library/Developer')) +POLICY = 'MACOS_OS_SELECTED_PHYSICAL_GIT_V1' +ROOT_WRITE_GROUPS = frozenset({0, 80}) # Darwin wheel/admin, not ordinary staff. +FIELDS = {'format', 'mode', 'operation_id', 'host_id', 'selection', 'executable', 'ancestors'} + + +def _metadata(info): + return {name: getattr(info, 'st_'+name) for name in + ('dev', 'ino', 'uid', 'gid', 'mode', 'size', 'mtime_ns', 'ctime_ns')} + + +def _ancestors(path): + rows = [] + for parent in path.parents: + info = parent.lstat() + if (not stat.S_ISDIR(info.st_mode) or info.st_uid not in {0, os.geteuid()} + or info.st_mode & 0o002 + or (info.st_mode & 0o020 and + (info.st_uid != 0 or info.st_gid not in ROOT_WRITE_GROUPS))): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_UNTRUSTED', 422) + rows.append({'path': str(parent), **{key: getattr(info, 'st_'+key) + for key in ('dev', 'ino', 'uid', 'gid', 'mode')}}) + return rows + + +def _binary(path): + if (not path.is_absolute() or '..' in path.parts or path.name != 'git' + or not any(path.is_relative_to(root) for root in ROOTS)): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_UNTRUSTED', 422) + ancestors = _ancestors(path) + with directory(path.parent) as parent: + before = os.stat(path.name, dir_fd=parent, follow_symlinks=False) + if (not stat.S_ISREG(before.st_mode) or before.st_uid not in {0, os.geteuid()} + or before.st_mode & 0o022 or not before.st_mode & 0o111): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_UNTRUSTED', 422) + _, sha256 = selected_bytes(parent, path.name, maximum=32*1024*1024, + deadline=time.monotonic()+2) + after = os.stat(path.name, dir_fd=parent, follow_symlinks=False) + if _metadata(before) != _metadata(after) or ancestors != _ancestors(path): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_CHANGED', 409) + return {'path': str(path), 'sha256': sha256, **_metadata(before)}, ancestors + + +def capture(*, operation_id: str, host_id: str) -> dict: + """Bounded host reads only; no Git, selector subprocess or target access.""" + if sys.platform != 'darwin': + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_MODE_UNSUPPORTED', 422) + try: + before = SELECTION.lstat() + if before.st_uid != 0 or not stat.S_ISLNK(before.st_mode): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_UNTRUSTED', 422) + link = str(SELECTION.readlink()) + path = (SELECTION/'usr/bin/git').resolve(strict=True) + executable, ancestors = _binary(path) + if _metadata(before) != _metadata(SELECTION.lstat()) or link != str(SELECTION.readlink()): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_CHANGED', 409) + return {'format': 1, 'mode': POLICY, 'operation_id': operation_id, 'host_id': host_id, + 'selection': {'path': str(SELECTION), 'link': link, **_metadata(before)}, + 'executable': executable, 'ancestors': ancestors} + except (OSError, InspectionError) as error: + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_UNAVAILABLE', 422) from error + + +def verify(pin, *, operation_id: str, host_id: str, selection: bool = False) -> str: + """The worker checks its binary; the controller also checks OS selection.""" + if (not isinstance(pin, dict) or set(pin) != FIELDS or type(pin['format']) is not int + or pin['format'] != 1 or pin['mode'] != POLICY + or pin['operation_id'] != operation_id or pin['host_id'] != host_id + or not isinstance(pin['selection'], dict) or not isinstance(pin['executable'], dict) + or not isinstance(pin['ancestors'], list)): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_PIN_INVALID', 409) + try: + path = Path(pin['executable']['path']) + if selection: + observed = capture(operation_id=operation_id, host_id=host_id) + match = canonical(observed) == canonical(pin) + else: + executable, ancestors = _binary(path) + match = (canonical(executable) == canonical(pin['executable']) + and canonical(ancestors) == canonical(pin['ancestors'])) + if not match: + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_CHANGED', 409) + except BootstrapError: + raise + except (OSError, InspectionError, KeyError, TypeError, ValueError) as error: + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_PIN_INVALID', 409) from error + return str(path) diff --git a/src/engineering_platform/genesis_service.py b/src/engineering_platform/genesis_service.py index 6016254b..9d7e4b18 100644 --- a/src/engineering_platform/genesis_service.py +++ b/src/engineering_platform/genesis_service.py @@ -23,7 +23,7 @@ import sys import time -from . import genesis_authority as authority, genesis_native, project_topology, local_repository_binding +from . import genesis_authority as authority, genesis_native, genesis_git_runtime, project_topology, local_repository_binding from .genesis_contract import BootstrapError, canonical, digest, identifier, request, load, MAX_BODY from .platform_admin import require_installation_owner from .preproject_inspection import directory, identity, selected_bytes @@ -209,6 +209,8 @@ def admit(root: Path, *, token, headers: dict, value: dict, source_identity=None target_digest = digest(canonical({"root_identity": scope["root_identity"], "target_name": scope["target_name"].casefold()})) at = authority.now().isoformat() evidence = {"known": {}, "completed_steps": [], "attempts": [], "accepted_at": int(datetime.fromisoformat(at).timestamp())} + if sys.platform == 'darwin': + evidence['git_runtime_policy'] = genesis_git_runtime.POLICY first = {"operation_id": operation_id, "grant_id": grant_id, "request_digest": scope["request_digest"], "target_identity": target_digest, "project_id": original.project_id, "repository_id": original.repository_id, "state": "RESERVED", "evidence": evidence, "created_at": at, "updated_at": at} @@ -287,6 +289,30 @@ def _execute_step(root: Path, *, operation_id: str, step: str, token, headers: d _save(db, row, token) job = {"scope": scope, "known": row["evidence"]["known"], "step": step, "accepted_at": row["evidence"]["accepted_at"], "bundle": row["request"]["bundle"]} + if sys.platform == 'darwin': + if row['evidence'].get('git_runtime_policy', genesis_git_runtime.POLICY) != genesis_git_runtime.POLICY: + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_PIN_INVALID', 409) + present, pending = _operation_git_pin(row) + if step == 'PREFLIGHT' and 'git_runtime' not in job['known']: + if ('PREFLIGHT' in row['evidence']['completed_steps'] + and not _legacy_git_pin_missing(row)): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_PIN_INVALID', 409) + job['git_runtime'] = (pending if present else + genesis_git_runtime.capture(operation_id=operation_id, host_id=scope['host_id'])) + else: + job['git_runtime'] = job['known'].get('git_runtime') + genesis_git_runtime.verify(job['git_runtime'], operation_id=operation_id, + host_id=scope['host_id'], selection=True) + # Preserve a selected candidate even when this attempt fails before + # PREFLIGHT completes. Explicit resume may verify it, never repin it. + # Only successful native receipt/logs/whole-group closure admit known. + with authority.store(root, write=True, expected_source=source_identity) as db: + current_grant, current_scope = _context(db, token, headers, operation_id) + fresh = _row(db, operation_id, token); _require_original(fresh, current_grant, current_scope) + if current_scope != scope or fresh['evidence']['attempts'][-1]['attempt_id'] != attempt['attempt_id']: + raise BootstrapError('BOOTSTRAP_SCOPE_CHANGED', 403) + fresh['evidence']['attempts'][-1]['git_runtime'] = job['git_runtime'] + _save(db, fresh, token) mutating = step not in {"PREFLIGHT", "VALIDATE_DOCS", "VERIFY_COMMIT"} target = Path(scope["workspace_root"]) / scope["target_name"] command = genesis_native.command(native=native, anchor=path / "a", target=target, @@ -386,6 +412,16 @@ def drain_tail(): _require_group_stopped(process.pid) if exit_code or result.get("state") != "STEP_COMPLETED" or result.get("step") != step: raise BootstrapError(result.get("error", "BOOTSTRAP_HOST_STEP_FAILED"), 409) + if sys.platform == 'darwin': + genesis_git_runtime.verify(job['git_runtime'], operation_id=operation_id, + host_id=scope['host_id'], selection=True) + if step == 'PREFLIGHT': + ready = result.get('result', {}) + version = ready.get('git_version_sha256') + if (canonical(ready.get('git_runtime')) != canonical(job['git_runtime']) + or not isinstance(version, str) or len(version) != 64 + or any(character not in '0123456789abcdef' for character in version)): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_RECEIPT_INVALID', 409) finally: primary = sys.exc_info()[1] cleanup_errors = [] @@ -454,7 +490,10 @@ def drain_tail(): with authority.store(root, write=True, expected_source=source_identity) as db: fresh = _row(db, operation_id, token) fresh["evidence"]["known"].update(result["result"]) - fresh["evidence"]["completed_steps"].append(step) + if step not in fresh['evidence']['completed_steps']: + fresh["evidence"]["completed_steps"].append(step) + if sys.platform == 'darwin' and step == 'PREFLIGHT': + fresh['evidence']['git_runtime_policy'] = genesis_git_runtime.POLICY fresh["evidence"]["attempts"][-1].update(state="STEP_COMPLETED", exit_code=exit_code) _save(db, fresh, token) return result["result"] @@ -521,6 +560,28 @@ def maintenance_activity(root: Path) -> bool: return True +def _legacy_git_pin_missing(row: dict) -> bool: + evidence = row['evidence'] + known = evidence['known'] + # These fields are protected by the original operation MAC. New-format + # admissions always carry the policy; never repair a broken new pin by + # treating a stale native-profile boolean alone as fresh readiness. + return (sys.platform == 'darwin' and 'git_runtime_policy' not in evidence + and 'git_runtime' not in known and known.get('native_profile_started') is True + and type(known.get('business_effects')) is int and known['business_effects'] == 0) + + +def _operation_git_pin(row: dict): + evidence = row['evidence'] + pins = ([evidence['known']['git_runtime']] if 'git_runtime' in evidence['known'] else []) + pins += [attempt['git_runtime'] for attempt in evidence['attempts'] if 'git_runtime' in attempt] + if not pins: + return False, None + if any(canonical(pin) != canonical(pins[0]) for pin in pins): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_PIN_INVALID', 409) + return True, pins[0] + + def execute(root: Path, *, operation_id: str, token, headers: dict, source_identity=None) -> None: with _lock(root, operation_id) as lock_fd: with authority.store(root, expected_source=source_identity) as db: @@ -529,11 +590,14 @@ def execute(root: Path, *, operation_id: str, token, headers: dict, source_ident _active_previous(row) if row["state"] == "COMPLETE": return + if sys.platform != 'darwin' and ('git_runtime_policy' in row['evidence'] or _operation_git_pin(row)[0]): + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_MODE_UNSUPPORTED', 422) try: for step in STEPS: with authority.store(root, expected_source=source_identity) as db: row = _row(db, operation_id, token) - if step in row["evidence"]["completed_steps"]: + legacy_runtime_missing = step == 'PREFLIGHT' and _legacy_git_pin_missing(row) + if step in row["evidence"]["completed_steps"] and not legacy_runtime_missing: continue _execute_step(root, operation_id=operation_id, step=step, token=token, headers=headers, source_identity=source_identity, lock_fd=lock_fd) diff --git a/src/engineering_platform/genesis_worker.py b/src/engineering_platform/genesis_worker.py index 15e7f0ed..107d7293 100644 --- a/src/engineering_platform/genesis_worker.py +++ b/src/engineering_platform/genesis_worker.py @@ -24,6 +24,7 @@ from .preproject_inspection import directory, identity, selected_bytes, inventory, read_relative from .repository_attachment import parse_repository_attachment from .effect_workspace import child_environment +from . import genesis_git_runtime @contextmanager @@ -234,8 +235,86 @@ def validate_documents(scope: dict, known: dict, files: dict, manifest_digest: s "in_repository_command_after_git": "NOT_QUALIFIED_OR_CLAIMED"} -def _git(root: Path, *args: str, known: dict, input_bytes: bytes | None = None) -> bytes: - command = ("git", "--git-dir=.", "--work-tree="+str(root), "-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", +def _git_executable(scope: dict, known: dict) -> str: + if sys.platform != 'darwin': + if 'git_runtime' in known: + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_MODE_UNSUPPORTED', 422) + return 'git' + return genesis_git_runtime.verify(known.get('git_runtime'), + operation_id=scope['operation_id'], host_id=scope['host_id']) + + +def _private_git_output(stdout: bytes, stderr: bytes) -> None: + # These are original received bytes, confined to the parent's private + # bounded stderr evidence. Never put them in the JSON receipt protocol. + failures = [] + for label, raw, maximum in ((b'STDOUT', stdout, 262144), (b'STDERR', stderr, 65536)): + if len(raw) > maximum: + failure = BootstrapError('BOOTSTRAP_GIT_OUTPUT_LIMIT' if label == b'STDOUT' + else 'BOOTSTRAP_GIT_ERROR_OUTPUT_LIMIT', 409) + failure.add_note(f'Original {label.decode()} bytes={len(raw)}, retained={maximum}') + failures.append(failure) + if raw: + try: + sys.stderr.buffer.write(b'\nGENESIS_GIT_'+label+b'\n'+raw[:maximum]) + except (OSError, ValueError) as error: + failures.append(error) + if failures: + raise ExceptionGroup('Original private Git output could not be preserved', failures) + + +def _run_git(command, metadata: int, input_bytes: bytes | None = None): + try: + result = subprocess.run(command, preexec_fn=lambda: os.fchdir(metadata), pass_fds=(metadata,), + env=child_environment(), input=input_bytes, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + timeout=5, check=False) + except subprocess.TimeoutExpired as error: + try: + _private_git_output(error.stdout or b'', error.stderr or b'') + except ExceptionGroup as evidence_error: + raise error from evidence_error + raise + primary = None + if result.returncode: + primary = BootstrapError('BOOTSTRAP_LOCAL_GIT_FAILED', 409) + elif len(result.stdout) > 262144: + primary = BootstrapError('BOOTSTRAP_GIT_OUTPUT_LIMIT', 409) + elif len(result.stderr) > 65536: + primary = BootstrapError('BOOTSTRAP_GIT_ERROR_OUTPUT_LIMIT', 409) + original_status = f'Original Git exit={result.returncode}, stdout_bytes={len(result.stdout)}, stderr_bytes={len(result.stderr)}' + if primary is not None: + primary.add_note(original_status) + try: + _private_git_output(result.stdout, result.stderr) + except ExceptionGroup as evidence_error: + if primary is not None: + raise primary from evidence_error + evidence_error.add_note(original_status) + raise + if primary is not None: + raise primary + return result.stdout + + +def preflight(scope: dict, runtime) -> dict: + result = {'native_profile_started': True, 'business_effects': 0} + if sys.platform != 'darwin': + return result + known = {'git_runtime': runtime} + executable = _git_executable(scope, known) + # Existing installed package read capability; the target is still absent. + # No additional native directory grants or filesystem writes are needed. + with directory(Path(__file__).resolve().parent) as metadata: + raw = _run_git((executable, '--version'), metadata) + _git_executable(scope, known) + if not raw.startswith(b'git version ') or len(raw.splitlines()) != 1: + raise BootstrapError('BOOTSTRAP_GIT_RUNTIME_RECEIPT_INVALID', 409) + return {**result, 'git_runtime': runtime, 'git_version_sha256': digest(raw)} + + +def _git(root: Path, *args: str, known: dict, scope: dict, input_bytes: bytes | None = None, + initializing_files: dict | None = None) -> bytes: + command = (_git_executable(scope, known), "--git-dir=.", "--work-tree="+str(root), "-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", "-c", "commit.gpgSign=false", "-c", "tag.gpgSign=false", "-c", "protocol.allow=never", "-c", "diff.autoRefreshIndex=false", "-c", "gc.auto=0", "-c", "gc.autoPackLimit=0", "-c", "maintenance.auto=false", *args) @@ -250,18 +329,21 @@ def _git(root: Path, *args: str, known: dict, input_bytes: bytes | None = None) raise BootstrapError("BOOTSTRAP_GIT_OWNERSHIP_UNCERTAIN", 409) # This dedicated worker is single-threaded; fchdir in its forked # child retains the validated inode through exec without a path race. - result = subprocess.run(command, preexec_fn=lambda: os.fchdir(metadata), pass_fds=(metadata,), - env=child_environment(), input=input_bytes, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - timeout=5, check=False) + # Recheck after the held target/metadata identities and immediately + # before execution. Same-owner hostile binary swaps are outside + # the explicitly qualified trusted-host boundary. + _git_executable(scope, known) + if initializing_files is not None: + verify_files(scope, known, initializing_files, git=True) + expected = scope['git_staging_identity'] + if (list(identity(os.fstat(metadata))) != expected or os.listdir(metadata) + or list(identity(os.stat('.git', dir_fd=parent, follow_symlinks=False))) != expected): + raise BootstrapError('BOOTSTRAP_GIT_OWNERSHIP_UNCERTAIN', 409) + raw = _run_git(command, metadata, input_bytes) finally: os.close(metadata) - if result.returncode: - # Original stderr is private step evidence, not a public error string. - sys.stderr.buffer.write(result.stderr[:65536]) - raise BootstrapError("BOOTSTRAP_LOCAL_GIT_FAILED", 409) - if len(result.stdout) > 262144: - raise BootstrapError("BOOTSTRAP_GIT_OUTPUT_LIMIT", 409) - return result.stdout + _git_executable(scope, known) + return raw def _git_root(scope: dict) -> Path: @@ -318,13 +400,34 @@ def git_initialize(scope: dict, known: dict, files: dict, *, prepared_parent_fd: if known.get("git_identity"): _git_guard(scope, known) return {k: known[k] for k in ("git_identity", "git_config_digest")} - verify_files(scope, known, files, git=False) - metadata_scope = {**scope, "workspace_root": str(_git_root(scope)), "root_identity": known["target_identity"], - "target_name": ".git", "staging_path": scope["git_staging_path"], "staging_identity": scope["git_staging_identity"], - "prepared_directory_identities": {}} - claim = create(metadata_scope, prepared_parent_fd=prepared_parent_fd) + recovered_empty = os.path.lexists(_git_root(scope)/'.git') + if recovered_empty: + # A failed original init may have published its granted empty inode + # before the tool shim timed out. This is not generic Git adoption: + # only that exact capability, still empty, can enter fixed Git init. + verify_files(scope, known, files, git=True) + with target(scope, known) as parent: + metadata = os.open('.git', os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=parent) + try: + expected = scope['git_staging_identity'] + if (list(identity(os.fstat(metadata))) != expected or os.listdir(metadata) + or list(identity(os.stat('.git', dir_fd=parent, follow_symlinks=False))) != expected): + raise BootstrapError('BOOTSTRAP_GIT_OWNERSHIP_UNCERTAIN', 409) + verify_files(scope, known, files, git=True) + if os.listdir(metadata): + raise BootstrapError('BOOTSTRAP_GIT_OWNERSHIP_UNCERTAIN', 409) + claim = {'target_identity': expected} + finally: + os.close(metadata) + else: + verify_files(scope, known, files, git=False) + metadata_scope = {**scope, "workspace_root": str(_git_root(scope)), "root_identity": known["target_identity"], + "target_name": ".git", "staging_path": scope["git_staging_path"], "staging_identity": scope["git_staging_identity"], + "prepared_directory_identities": {}} + claim = create(metadata_scope, prepared_parent_fd=prepared_parent_fd) initializing = {**known, "git_identity": claim["target_identity"]} - _git(_git_root(scope), "-c", "init.templateDir=", "init", "--initial-branch=main", known=initializing) + _git(_git_root(scope), "-c", "init.templateDir=", "init", "--initial-branch=main", known=initializing, + scope=scope, initializing_files=files) with target(scope, known) as fd: gitfd = os.open(".git", os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd) try: @@ -333,7 +436,8 @@ def git_initialize(scope: dict, known: dict, files: dict, *, prepared_parent_fd: _, config_digest = selected_bytes(gitfd, "config", maximum=65536, deadline=time.monotonic()+2) if identity(os.stat(".git", dir_fd=fd, follow_symlinks=False)) != identity(os.fstat(gitfd)): raise BootstrapError("BOOTSTRAP_GIT_OWNERSHIP_UNCERTAIN", 409) - return {"git_identity": claim["target_identity"], "git_config_digest": config_digest} + return {"git_identity": claim["target_identity"], "git_config_digest": config_digest, + 'recovered_original_empty_git_capability': recovered_empty} finally: os.close(gitfd) @@ -356,7 +460,7 @@ def prepare_commit(scope: dict, known: dict, files: dict, accepted_at: int) -> d node[parts[-1]] = expected expected_blobs.append(expected) blob_commands.append(b"blob\nmark :"+str(mark).encode()+b"\ndata "+str(len(raw)).encode()+b"\n"+raw+b"\nget-mark :"+str(mark).encode()+b"\n") - actual_blobs = _git(root, "fast-import", "--quiet", "--done", known=known, + actual_blobs = _git(root, "fast-import", "--quiet", "--done", known=known, scope=scope, input_bytes=b"".join(blob_commands)+b"done\n").decode().splitlines() if actual_blobs != expected_blobs: raise BootstrapError("BOOTSTRAP_BLOB_CONFLICT", 409) @@ -377,10 +481,10 @@ def plan_tree(node): expected_trees.append(object_id) return object_id tree = plan_tree(hierarchy) - actual_trees = _git(root, "mktree", "--batch", "-z", known=known, input_bytes=b"".join(tree_commands)).decode().splitlines() + actual_trees = _git(root, "mktree", "--batch", "-z", known=known, scope=scope, input_bytes=b"".join(tree_commands)).decode().splitlines() if actual_trees != expected_trees: raise BootstrapError("BOOTSTRAP_TREE_CONFLICT", 409) - _git(root, "read-tree", tree, known=known) + _git(root, "read-tree", tree, known=known, scope=scope) declaration = b"tree " + tree.encode() + b"\nauthor Engineering Platform Bootstrap " author = str(accepted_at).encode() + b" +0000" raw = declaration + author + b"\ncommitter Engineering Platform Bootstrap " + author @@ -395,10 +499,10 @@ def commit_object(scope: dict, known: dict) -> dict: root = _git_root(scope) object_path = root / ".git/objects" / known["commit"][:2] / known["commit"][2:] if os.path.lexists(object_path): - if _git(root, "cat-file", "commit", known["commit"], known=known) != raw: + if _git(root, "cat-file", "commit", known["commit"], known=known, scope=scope) != raw: raise BootstrapError("BOOTSTRAP_COMMIT_CONFLICT", 409) return {"commit_object": known["commit"], "recovered_existing_object": True} - actual = _git(root, "hash-object", "-t", "commit", "-w", "--stdin", known=known, input_bytes=raw).decode().strip() + actual = _git(root, "hash-object", "-t", "commit", "-w", "--stdin", known=known, scope=scope, input_bytes=raw).decode().strip() if actual != known["commit"]: raise BootstrapError("BOOTSTRAP_COMMIT_CONFLICT", 409) return {"commit_object": actual} @@ -407,16 +511,16 @@ def commit_object(scope: dict, known: dict) -> dict: def commit_ref(scope: dict, known: dict) -> dict: _git_guard(scope, known) root = _git_root(scope) - raw = _git(root, "cat-file", "commit", known["commit"], known=known) + raw = _git(root, "cat-file", "commit", known["commit"], known=known, scope=scope) if raw != known["commit_utf8"].encode(): raise BootstrapError("BOOTSTRAP_COMMIT_CONFLICT", 409) reference = root / ".git/refs/heads/main" if os.path.lexists(reference): - if _git(root, "rev-parse", "refs/heads/main", known=known).decode().strip() != known["commit"]: + if _git(root, "rev-parse", "refs/heads/main", known=known, scope=scope).decode().strip() != known["commit"]: raise BootstrapError("BOOTSTRAP_COMMIT_CONFLICT", 409) return {"commit_ref": known["commit"], "recovered_existing_ref": True} # Creation-only CAS; an unrelated concurrent ref is never overwritten. - _git(root, "update-ref", "refs/heads/main", known["commit"], "0"*40, known=known) + _git(root, "update-ref", "refs/heads/main", known["commit"], "0"*40, known=known, scope=scope) return {"commit_ref": known["commit"]} @@ -425,11 +529,11 @@ def verify_commit(scope: dict, known: dict, files: dict) -> dict: verify_files(scope, known, files, git=True) before = git_snapshot(scope, known) root = _git_root(scope) - if _git(root, "rev-parse", "HEAD", known=known).decode().strip() != known["commit"]: + if _git(root, "rev-parse", "HEAD", known=known, scope=scope).decode().strip() != known["commit"]: raise BootstrapError("BOOTSTRAP_COMMIT_CONFLICT", 409) - if _git(root, "cat-file", "commit", known["commit"], known=known) != known["commit_utf8"].encode(): + if _git(root, "cat-file", "commit", known["commit"], known=known, scope=scope) != known["commit_utf8"].encode(): raise BootstrapError("BOOTSTRAP_COMMIT_CONFLICT", 409) - rows = _git(root, "ls-tree", "-rz", "--full-tree", known["commit"], known=known).split(b"\0") + rows = _git(root, "ls-tree", "-rz", "--full-tree", known["commit"], known=known, scope=scope).split(b"\0") observed = {} for row in filter(None, rows): info, name = row.split(b"\t", 1) @@ -438,9 +542,9 @@ def verify_commit(scope: dict, known: dict, files: dict) -> dict: if mode != "100644" or kind != "blob" or name not in files: raise BootstrapError("BOOTSTRAP_COMMITTED_TREE_CONFLICT", 409) observed[name] = object_id - if set(observed) != set(files) or _git(root, "rev-parse", "HEAD^{tree}", known=known).decode().strip() != known["tree"]: + if set(observed) != set(files) or _git(root, "rev-parse", "HEAD^{tree}", known=known, scope=scope).decode().strip() != known["tree"]: raise BootstrapError("BOOTSTRAP_COMMITTED_TREE_CONFLICT", 409) - batch = _git(root, "cat-file", "--batch", known=known, + batch = _git(root, "cat-file", "--batch", known=known, scope=scope, input_bytes=b"".join(observed[name].encode()+b"\n" for name in sorted(files))) for name in sorted(files): header, batch = batch.split(b"\n", 1) @@ -451,7 +555,7 @@ def verify_commit(scope: dict, known: dict, files: dict) -> dict: raise BootstrapError("BOOTSTRAP_COMMITTED_TREE_CONFLICT", 409) batch = batch[size+1:] observed[name] = digest(files[name]) - if batch or _git(root, "diff-index", "--cached", "--name-only", known["commit"], known=known): + if batch or _git(root, "diff-index", "--cached", "--name-only", known["commit"], known=known, scope=scope): raise BootstrapError("BOOTSTRAP_COMMITTED_INDEX_CONFLICT", 409) declaration = parse_repository_attachment(load(files[".engineering-platform/repository.json"])) if before != git_snapshot(scope, known): @@ -470,7 +574,7 @@ def main() -> int: if original.bundle_digest != scope["bundle_digest"]: raise BootstrapError("BOOTSTRAP_WORKER_BINDING_CONFLICT", 409) functions = { - "PREFLIGHT": lambda: {"native_profile_started": True, "business_effects": 0}, + "PREFLIGHT": lambda: preflight(scope, job.get('git_runtime')), "CREATE_TARGET": lambda: create(scope), "WRITE_FILES": lambda: write_files(scope, known, original.files), "VALIDATE_DOCS": lambda: validate_documents(scope, known, original.files, original.manifest_digest), @@ -493,7 +597,7 @@ def main() -> int: result = functions[step]() print(canonical({"state": "STEP_COMPLETED", "step": step, "result": result}).decode(), end="", flush=True) return 0 - except (BootstrapError, OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError) as error: + except (BootstrapError, OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError, ExceptionGroup) as error: # This stream is the owning private step evidence. Preserve the actual # failure at occurrence, never reconstruct missing historical stderr. traceback.print_exc(file=sys.stderr) diff --git a/src/engineering_platform/platform_version.py b/src/engineering_platform/platform_version.py index 8f5038b7..c2bb9316 100644 --- a/src/engineering_platform/platform_version.py +++ b/src/engineering_platform/platform_version.py @@ -12,7 +12,7 @@ SEMVER = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$") CONTRACT = re.compile(r"^(\d{4})\.(0[1-9]|1[0-2])$") -CURRENT_PLATFORM_VERSION = "2.3.123" +CURRENT_PLATFORM_VERSION = "2.3.124" MANIFEST_FIELDS = frozenset( { "platform_version", diff --git a/src/engineering_platform/templates/workspace-config.json b/src/engineering_platform/templates/workspace-config.json index a128f286..dd65eee8 100644 --- a/src/engineering_platform/templates/workspace-config.json +++ b/src/engineering_platform/templates/workspace-config.json @@ -3,7 +3,7 @@ "platform": { "id": "engineering-platform", "name": "Engineering Platform", - "version": "2.3.123", + "version": "2.3.124", "generation": 2, "documentation_namespace": "engineering-platform", "capability_registry_version": 1 diff --git a/tests/engineering/test_genesis_bootstrap.py b/tests/engineering/test_genesis_bootstrap.py index 2cecf37c..a228e59c 100644 --- a/tests/engineering/test_genesis_bootstrap.py +++ b/tests/engineering/test_genesis_bootstrap.py @@ -45,7 +45,12 @@ def _create(self, scope): from engineering_platform import genesis_worker as worker from engineering_platform.preproject_inspection import directory with directory(Path(scope["staging_path"]).parent) as fd: - return worker.create(scope, prepared_parent_fd=fd) + known = worker.create(scope, prepared_parent_fd=fd) + if sys.platform == 'darwin': + from engineering_platform import genesis_git_runtime + known['git_runtime'] = genesis_git_runtime.capture( + operation_id=scope['operation_id'], host_id=scope['host_id']) + return known def _git_initialize(self, scope, known, files): from engineering_platform import genesis_worker as worker @@ -248,7 +253,7 @@ def test_valid_current_committed_capability_registers_once(self): def test_committed_genesis_has_clean_index_and_worktree(self): from engineering_platform import genesis_worker as worker scope, known = self._admitted_committed() - self.assertEqual(worker._git(self.workspace / scope["target_name"], "status", "--porcelain=v1", known=known), b"") + self.assertEqual(worker._git(self.workspace / scope["target_name"], "status", "--porcelain=v1", known=known, scope=scope), b"") def test_foreign_git_metadata_is_not_adopted_at_creation(self): from engineering_platform import genesis_worker as worker diff --git a/tests/engineering/test_genesis_contract_and_cli.py b/tests/engineering/test_genesis_contract_and_cli.py index d3657000..bab65988 100644 --- a/tests/engineering/test_genesis_contract_and_cli.py +++ b/tests/engineering/test_genesis_contract_and_cli.py @@ -139,14 +139,19 @@ def test_exact_file_recovery_cannot_accept_changed_modes_bytes_or_extra_entries( def test_worker_protocol_requires_exact_original_bundle_step_and_fresh_permit(self): from engineering_platform import genesis_worker as worker - job = {'scope': {'bundle_digest': contract.bundle(self.bundle).bundle_digest}, 'known': {}, + with authority.store(self.data) as db: + _, scope = authority.authenticate(db, self.grant['credential'], self.headers) + from engineering_platform import genesis_git_runtime + runtime = (genesis_git_runtime.capture(operation_id=scope['operation_id'], host_id=scope['host_id']) + if sys.platform == 'darwin' else None) + job = {'scope': scope, 'known': {}, 'git_runtime': runtime, 'step': 'PREFLIGHT', 'bundle': self.bundle, 'accepted_at': 1792137600} for step, bound, permit, expected in (('PREFLIGHT', job['scope']['bundle_digest'], b'EXECUTE\n', 0), ('PREFLIGHT', job['scope']['bundle_digest'], b'NO\n', 1), ('UNKNOWN', job['scope']['bundle_digest'], b'EXECUTE\n', 1), ('PREFLIGHT', '0'*64, b'EXECUTE\n', 1)): value = deepcopy(job); value['step'] = step; value['scope']['bundle_digest'] = bound - output, errors = io.StringIO(), io.StringIO() + output, errors = io.StringIO(), io.TextIOWrapper(io.BytesIO()) input_stream = io.TextIOWrapper(io.BytesIO(contract.canonical(value)+permit)) with patch.object(sys, 'stdin', input_stream), redirect_stdout(output), patch.object(sys, 'stderr', errors): self.assertEqual(worker.main(), expected) @@ -164,7 +169,7 @@ def test_real_controller_child_handshake_permit_and_durable_logs(self): 'raw=(json.dumps(j,sort_keys=True,ensure_ascii=False,separators=(",",":"))+"\\n").encode(); ' 'print(json.dumps({"state":"STEP_READY","job_digest":hashlib.sha256(raw).hexdigest()}),flush=True); ' 'assert sys.stdin.buffer.readline()==b"EXECUTE\\n"; ' - 'print(json.dumps({"state":"STEP_COMPLETED","step":j["step"],"result":{"business_effects":0}}),flush=True)') + 'print(json.dumps({"state":"STEP_COMPLETED","step":j["step"],"result":{"business_effects":0,"git_runtime":j.get("git_runtime"),"git_version_sha256":"0"*64}}),flush=True)') original_popen = subprocess.Popen def initialized_child(*args, **kwargs): # Wait for this modeled worker's actual exec before returning its diff --git a/tests/engineering/test_genesis_git_controller.py b/tests/engineering/test_genesis_git_controller.py new file mode 100644 index 00000000..608a839b --- /dev/null +++ b/tests/engineering/test_genesis_git_controller.py @@ -0,0 +1,142 @@ +"""Portable controller regressions, not native/macOS qualification. +Actual controller pipe/identity/log behavior with a protocol-only subprocess. +OS selection is modeled; real installed native proof remains separate. +""" +from copy import deepcopy +import json +from pathlib import Path +import subprocess +import sys +import time +from types import SimpleNamespace +import unittest +from unittest.mock import patch +from tests.engineering import test_genesis_bootstrap as fixture +from engineering_platform import genesis_authority as authority,genesis_contract as contract,genesis_service as service + +class PortableControllerPinRegressions(unittest.TestCase): + setUp=fixture.GenesisOwnerFixture.setUp + def prepared(self): + service.admit(self.data,token=self.grant['credential'],headers=self.headers,value=self.original_request) + with authority.store(self.data) as db: + _,scope=authority.authenticate(db,self.grant['credential'],self.headers) + return {'format':1,'mode':service.genesis_git_runtime.POLICY,'operation_id':scope['operation_id'],'host_id':scope['host_id'],'selection':{'fixture':'MODELED_OS_SELECTION'},'executable':{'path':'/MODELED/NEVER_EXECUTED/git'},'ancestors':[]} + def evidence(self): + with authority.store(self.data) as db:return service._row(db,'synthetic-operation',self.grant['credential'])['evidence'] + def run_readiness(self,pin,receipt=None,verify=None): + marker=self.root/'protocol-worker-ready' + result={'business_effects':0,'git_runtime':pin,'git_version_sha256':'0'*64} if receipt is None else receipt + code=(f'import sys,json,hashlib;from pathlib import Path;Path({str(marker)!r}).write_text("ready"); ' + 'j=json.loads(sys.stdin.buffer.readline()); ' + 'raw=(json.dumps(j,sort_keys=True,ensure_ascii=False,separators=(",",":"))+"\\n").encode(); ' + 'print(json.dumps({"state":"STEP_READY","job_digest":hashlib.sha256(raw).hexdigest()}),flush=True); ' + 'assert sys.stdin.buffer.readline()==b"EXECUTE\\n"; ' + f'print(json.dumps({{"state":"STEP_COMPLETED","step":j["step"],"result":{result!r}}}),flush=True)') + original_popen=subprocess.Popen + self.starts=[] + def initialized(*args,**kwargs): + self.starts.append(1) + self.assertEqual(self.evidence()["attempts"][-1]["git_runtime"],pin) + self.assertNotIn("git_runtime",self.evidence()["known"]) + p=original_popen(*args,**kwargs) + until=time.monotonic()+5 + while not marker.exists(): + if p.poll() is not None or time.monotonic()>=until: + p.kill();p.wait(timeout=5);self.fail('Own protocol child did not reach exec marker') + time.sleep(.01) + return p + modeled_sys=SimpleNamespace(**{k:getattr(sys,k) for k in dir(sys) if k!='platform'},platform='darwin') + with service._lock(self.data,'synthetic-operation') as fd, \ + patch.object(service,'sys',modeled_sys), \ + patch.object(service.genesis_native,'runtime',return_value=Path(sys.executable)), \ + patch.object(service.genesis_native,'command',return_value=(sys.executable,'-I','-c',code)), \ + patch.object(service.genesis_git_runtime,'capture',return_value=pin) as capture, \ + patch.object(service.genesis_git_runtime,'verify',side_effect=verify,return_value=pin['executable']['path']) as checked, \ + patch.object(service.subprocess,'Popen',side_effect=initialized): + result=service._execute_step(self.data,operation_id='synthetic-operation',step='PREFLIGHT',token=self.grant['credential'],headers=self.headers,source_identity=None,lock_fd=fd) + return result,capture.call_count,checked.call_args_list + def test_modeled_macos_readiness_persists_same_original_pin_after_real_handshake(self): + pin=self.prepared(); result,captured,checks=self.run_readiness(pin) + self.assertEqual(captured,1);self.assertEqual(len(checks),2) + self.assertTrue(all(c.kwargs['selection'] for c in checks)) + e=self.evidence();self.assertEqual(e['known']['git_runtime'],pin) + self.assertEqual(e['attempts'][0]['git_runtime'],pin) + self.assertEqual(e['completed_steps'],['PREFLIGHT']) + self.assertEqual(e['attempts'][0]['state'],'STEP_COMPLETED') + self.assertFalse((self.workspace/'new-repository').exists()) + def test_foreign_worker_pin_is_red_and_never_becomes_known(self): + pin=self.prepared();foreign=deepcopy(pin);foreign['host_id']='foreign' + receipt={'business_effects':0,'git_runtime':foreign,'git_version_sha256':'0'*64} + with self.assertRaisesRegex(contract.BootstrapError,'RECEIPT_INVALID'):self.run_readiness(pin,receipt) + e=self.evidence();self.assertNotIn('git_runtime',e['known']);self.assertEqual(e['completed_steps'],[]) + self.assertEqual(e['attempts'][0]['git_runtime'],pin) + self.assertFalse((self.workspace/'new-repository').exists()) + def test_selection_drift_after_worker_exit_cannot_complete_readiness(self): + pin=self.prepared() + with self.assertRaisesRegex(contract.BootstrapError,'GIT_RUNTIME_CHANGED'): + self.run_readiness(pin,verify=[pin['executable']['path'],contract.BootstrapError('BOOTSTRAP_GIT_RUNTIME_CHANGED',409)]) + e=self.evidence();self.assertNotIn('git_runtime',e['known']);self.assertEqual(e['completed_steps'],[]) + self.assertEqual(e['attempts'][0]['git_runtime'],pin) + self.assertFalse((self.workspace/'new-repository').exists()) + def test_failed_attempt_pin_is_reused_without_capture(self): + pin=self.prepared() + with authority.store(self.data,write=True) as db: + row=service._row(db,'synthetic-operation',self.grant['credential']) + row['evidence']['attempts']=[{'attempt_id':'a'*32,'step':'PREFLIGHT','state':'INTENT_RECORDED','git_runtime':pin}] + service._save(db,row,self.grant['credential']) + result,captured,checks=self.run_readiness(pin) + self.assertEqual(captured,0);self.assertEqual(len(checks),2) + e=self.evidence();self.assertEqual(e['attempts'][0]['git_runtime'],pin) + self.assertEqual(e['known']['git_runtime'],pin);self.assertEqual(e['completed_steps'],['PREFLIGHT']) + + def modeled_platform(self,name): + return SimpleNamespace(**{k:getattr(sys,k) for k in dir(sys) if k!='platform'},platform=name) + def test_legacy_readiness_is_selected_and_history_is_not_rewritten(self): + pin=self.prepared() + historical={'attempt_id':'a'*32,'step':'PREFLIGHT','state':'STEP_COMPLETED','exit_code':0} + with authority.store(self.data,write=True) as db: + row=service._row(db,'synthetic-operation',self.grant['credential']) + row['evidence'].pop('git_runtime_policy',None) + row['evidence'].update(completed_steps=['PREFLIGHT'],attempts=[historical],known={'native_profile_started':True,'business_effects':0}) + service._save(db,row,self.grant['credential']) + with patch.object(service,'sys',self.modeled_platform('darwin')),patch.object(service,'_execute_step',side_effect=contract.BootstrapError('OWN_STOP',409)) as step: + with self.assertRaisesRegex(contract.BootstrapError,'OWN_STOP'): + service.execute(self.data,operation_id='synthetic-operation',token=self.grant['credential'],headers=self.headers) + self.assertEqual(step.call_args.kwargs['step'],'PREFLIGHT') + self.assertEqual(self.evidence()['attempts'],[historical]) + self.run_readiness(pin) + e=self.evidence();self.assertEqual(e['completed_steps'],['PREFLIGHT']) + self.assertEqual(e['attempts'][0],historical);self.assertEqual(len(e['attempts']),2) + self.assertEqual(e['git_runtime_policy'],service.genesis_git_runtime.POLICY) + def test_broken_current_pin_cannot_be_treated_as_legacy(self): + pin=self.prepared() + with authority.store(self.data,write=True) as db: + row=service._row(db,'synthetic-operation',self.grant['credential']) + row['evidence'].update(git_runtime_policy=service.genesis_git_runtime.POLICY,completed_steps=['PREFLIGHT'],known={'native_profile_started':True,'business_effects':0}) + service._save(db,row,self.grant['credential']) + with self.assertRaisesRegex(contract.BootstrapError,'PIN_INVALID'):self.run_readiness(pin) + self.assertEqual(self.starts,[]);self.assertNotIn('git_runtime',self.evidence()['known']) + self.assertFalse((self.workspace/'new-repository').exists()) + def test_macos_pin_on_non_darwin_refuses_before_runtime_or_process_start(self): + pin=self.prepared() + with authority.store(self.data,write=True) as db: + row=service._row(db,'synthetic-operation',self.grant['credential']) + row['evidence']['known']['git_runtime']=pin + service._save(db,row,self.grant['credential']) + before=self.evidence() + with patch.object(service,'sys',self.modeled_platform('linux')),patch.object(service.genesis_native,'runtime') as native,patch.object(service.subprocess,'Popen') as start: + with self.assertRaisesRegex(contract.BootstrapError,'MODE_UNSUPPORTED'): + service.execute(self.data,operation_id='synthetic-operation',token=self.grant['credential'],headers=self.headers) + native.assert_not_called();start.assert_not_called() + self.assertEqual(before,self.evidence());self.assertFalse((self.workspace/'new-repository').exists()) + def test_withdrawal_after_selection_prevents_pin_publication_and_start(self): + pin=self.prepared() + def withdraw(*args,**kwargs): + with authority.store(self.data,write=True) as db: + authority.revoke(db,grant_id='synthetic-grant',operation_id='owner-withdraw') + return pin['executable']['path'] + with self.assertRaises(contract.BootstrapError):self.run_readiness(pin,verify=withdraw) + self.assertEqual(self.starts,[]) + self.assertFalse((self.workspace/'new-repository').exists()) + +if __name__=='__main__':unittest.main() diff --git a/tests/engineering/test_genesis_git_probe_diagnostics.py b/tests/engineering/test_genesis_git_probe_diagnostics.py new file mode 100644 index 00000000..a9546b2a --- /dev/null +++ b/tests/engineering/test_genesis_git_probe_diagnostics.py @@ -0,0 +1,81 @@ +"""Real protocol-child fault regressions; these are not native Genesis PASS.""" +import importlib.util +import json +from pathlib import Path +import subprocess +import sys +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +from engineering_platform import genesis_native + +SPEC = importlib.util.spec_from_file_location('git_host_probe', Path(__file__).resolve().parents[2] / + 'tools/qualification/genesis_git_runtime_probe.py') +probe = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(probe) + + +class GitProbeEvidenceTests(unittest.TestCase): + def test_worker_timeout_retains_received_bytes(self): + setup = '''import subprocess +def fail(*a,**k):raise subprocess.TimeoutExpired('fixed-own-protocol-fixture',5,output=b'original-out',stderr=b'original-error') +subprocess.run=fail +''' + actual = subprocess.run((sys.executable, '-I', '-c', setup+probe.WORKER, 'fixed-own-protocol-fixture', str(self.root/'r')), + cwd=self.root, capture_output=True, text=True, timeout=5, check=True) + result = json.loads(actual.stdout) + self.assertEqual(result['error_type'], 'TimeoutExpired') + self.assertEqual(bytes.fromhex(result['stdout_hex']), b'original-out') + self.assertEqual(bytes.fromhex(result['stderr_hex']), b'original-error') + + def setUp(self): + temporary = tempfile.TemporaryDirectory(prefix='own-git-probe-log-') + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + (self.root/'a').mkdir() + (self.root/'r').mkdir() + self.output = self.root/'out' + (self.output/'private').mkdir(parents=True) + self.args = SimpleNamespace(python=Path(sys.executable), output=self.output) + + def run_protocol_child(self, code, label): + # A fixed owning Python protocol fixture only; the actual native/macOS + # runtime comparison remains a separate installed host qualification. + with patch.object(genesis_native, 'runtime', return_value=Path(sys.executable)), \ + patch.object(genesis_native, 'command', return_value=(sys.executable, '-I', '-c', code)): + return probe.one(self.args, self.root, label, 'git') + + def test_actual_stderr_overflow_keeps_original_and_cleanup_logs(self): + code = 'import sys,json;sys.stderr.buffer.write(b"x"*2097152);sys.stderr.flush();print(json.dumps({"returncode":0}),flush=True)' + result = self.run_protocol_child(code, 'overflow') + self.assertEqual(result['probe_error_type'], 'RuntimeError') + self.assertEqual(result['control_integrity'], 'FAIL') + self.assertTrue((self.output/'private/overflow.stdout').is_file()) + saved = self.output/'private/overflow.stderr' + self.assertEqual(saved.stat().st_size, 262144) + record = json.loads((self.output/'private/original-controls.jsonl').read_text()) + self.assertEqual(record['probe_error_type'], 'RuntimeError') + self.assertTrue(record['cleanup_errors']) + + def test_extra_stdout_is_retained_and_rejected(self): + result = self.run_protocol_child( + 'import sys;sys.stdout.buffer.write(b\'{"returncode":0}\\nUNEXPECTED-TAIL\\n\');sys.stdout.flush()', 'tail') + self.assertEqual(result['control_integrity'], 'FAIL') + self.assertIn(b'UNEXPECTED-TAIL\n', (self.output/'private/tail.stdout').read_bytes()) + + def test_stdout_sink_failure_does_not_skip_original_stderr(self): + code = 'import sys,json;sys.stderr.buffer.write(b"original-stderr");sys.stderr.flush();print(json.dumps({"returncode":0}),flush=True)' + original_write = Path.write_bytes + def fail_stdout(path, data): + if path.name == 'sink-failure.stdout': raise OSError('own exact stdout sink fault') + return original_write(path, data) + with patch.object(Path, 'write_bytes', fail_stdout): + result = self.run_protocol_child(code, 'sink-failure') + self.assertEqual((self.output/'private/sink-failure.stderr').read_bytes(), b'original-stderr') + self.assertEqual(result['control_integrity'], 'FAIL') + self.assertTrue(any(error['type'] == 'OSError' for error in result['cleanup_errors'])) + + +if __name__ == '__main__': unittest.main() diff --git a/tests/engineering/test_genesis_git_recovery.py b/tests/engineering/test_genesis_git_recovery.py new file mode 100644 index 00000000..d99a8039 --- /dev/null +++ b/tests/engineering/test_genesis_git_recovery.py @@ -0,0 +1,195 @@ +"""Original capability recovery and controller migration; synthetic owners only.""" +from copy import deepcopy +from pathlib import Path +import subprocess +import sys +import unittest +from unittest.mock import patch + +from engineering_platform import genesis_authority as authority, genesis_contract as contract +from engineering_platform import genesis_service as service, genesis_worker as worker +from tests.engineering import test_genesis_bootstrap as fixture + + +class OriginalGitRecoveryTests(unittest.TestCase): + setUp = fixture.GenesisOwnerFixture.setUp + _create = fixture.GenesisOwnerFixture._create + _git_initialize = fixture.GenesisOwnerFixture._git_initialize + + def prepared(self): + with authority.store(self.data) as db: + _, scope = authority.authenticate(db, self.grant['credential'], self.headers) + files = contract.bundle(self.bundle).files + known = self._create(scope) + known.update(worker.write_files(scope, known, files)) + return scope, known, files + + def failed_original_publication(self): + scope, known, files = self.prepared() + with patch.object(worker, '_run_git', side_effect=subprocess.TimeoutExpired('fixed own init', 5)): + with self.assertRaises(subprocess.TimeoutExpired): + self._git_initialize(scope, known, files) + metadata = self.workspace/scope['target_name']/'.git' + self.assertEqual(list(metadata.iterdir()), []) + self.assertEqual([metadata.stat().st_dev,metadata.stat().st_ino], scope['git_staging_identity']) + return scope, known, files, metadata + + def test_original_empty_capability_recovers_without_new_inode_or_documents(self): + scope, known, files, metadata = self.failed_original_publication() + original = metadata.stat().st_ino + receipt = self._git_initialize(scope, known, files) + self.assertTrue(receipt['recovered_original_empty_git_capability']) + self.assertEqual(metadata.stat().st_ino, original) + self.assertEqual(receipt['git_identity'], scope['git_staging_identity']) + worker.verify_files(scope, known, files, git=True) + self.assertFalse(Path(scope['git_staging_path']).exists()) + + def test_foreign_empty_inode_is_never_adopted(self): + scope, known, files = self.prepared() + metadata = self.workspace/scope['target_name']/'.git'; metadata.mkdir() + before = metadata.stat().st_ino + with patch.object(worker, '_run_git') as git: + with self.assertRaisesRegex(contract.BootstrapError, 'OWNERSHIP_UNCERTAIN'): + self._git_initialize(scope, known, files) + git.assert_not_called() + self.assertEqual(metadata.stat().st_ino, before) + self.assertEqual(list(metadata.iterdir()), []) + self.assertTrue(Path(scope['git_staging_path']).exists()) + + def test_unproven_nonempty_original_inode_is_preserved_and_refused(self): + scope, known, files, metadata = self.failed_original_publication() + (metadata/'unproven').write_bytes(b'preserve this original unproved state') + with patch.object(worker, '_run_git') as git: + with self.assertRaisesRegex(contract.BootstrapError, 'OWNERSHIP_UNCERTAIN'): + self._git_initialize(scope, known, files) + git.assert_not_called() + self.assertEqual((metadata/'unproven').read_bytes(), b'preserve this original unproved state') + + def test_changed_document_prevents_even_original_empty_inode_recovery(self): + scope, known, files, metadata = self.failed_original_publication() + name = next(iter(files)) + (self.workspace/scope['target_name']/name).write_bytes(b'changed original manifest file') + with patch.object(worker, '_run_git') as git: + with self.assertRaises(contract.BootstrapError): self._git_initialize(scope, known, files) + git.assert_not_called() + self.assertEqual(list(metadata.iterdir()), []) + + @unittest.skipUnless(sys.platform == 'darwin', 'macOS runtime migration only') + def test_only_missing_legacy_pin_gets_new_readiness_attempt_without_history_rewrite(self): + service.admit(self.data, token=self.grant['credential'], headers=self.headers, value=self.original_request) + historical = {'attempt_id':'a'*32,'step':'PREFLIGHT','state':'STEP_COMPLETED','exit_code':0} + with authority.store(self.data, write=True) as db: + row = service._row(db, 'synthetic-operation', self.grant['credential']) + row['evidence'].pop('git_runtime_policy') + row['evidence']['completed_steps'] = ['PREFLIGHT'] + row['evidence']['attempts'] = [historical] + row['evidence']['known'] = {'native_profile_started':True,'business_effects':0} + service._save(db, row, self.grant['credential']) + # Selection of the next finite step only. Real installed migration and + # native execution remain separate qualification, not this double. + with patch.object(service, '_execute_step', side_effect=contract.BootstrapError('OWN_STOP',409)) as step: + with self.assertRaisesRegex(contract.BootstrapError, 'OWN_STOP'): + service.execute(self.data, operation_id='synthetic-operation', token=self.grant['credential'], headers=self.headers) + self.assertEqual(step.call_args.kwargs['step'], 'PREFLIGHT') + with authority.store(self.data) as db: + row = service._row(db, 'synthetic-operation', self.grant['credential']) + self.assertEqual(row['evidence']['completed_steps'], ['PREFLIGHT']) + self.assertEqual(row['evidence']['attempts'], [historical]) + self.assertFalse((self.workspace/'new-repository').exists()) + + @unittest.skipUnless(sys.platform == 'darwin', 'macOS runtime migration only') + def test_current_missing_pin_is_red_without_repin_or_worker_start(self): + service.admit(self.data, token=self.grant['credential'], headers=self.headers, value=self.original_request) + with authority.store(self.data, write=True) as db: + row = service._row(db, 'synthetic-operation', self.grant['credential']) + row['evidence']['completed_steps'] = ['PREFLIGHT'] + service._save(db, row, self.grant['credential']) + with patch.object(service.genesis_native, 'runtime', return_value=Path(sys.executable)), \ + patch.object(service.genesis_git_runtime, 'capture') as capture, \ + patch.object(service.subprocess, 'Popen') as start: + with self.assertRaisesRegex(contract.BootstrapError, 'PIN_INVALID'): + service.execute(self.data, operation_id='synthetic-operation', token=self.grant['credential'], headers=self.headers) + capture.assert_not_called(); start.assert_not_called() + self.assertFalse((self.workspace/'new-repository').exists()) + + def test_revoked_original_scope_starts_no_readiness_or_worker(self): + service.admit(self.data, token=self.grant['credential'], headers=self.headers, value=self.original_request) + with authority.store(self.data, write=True) as db: + authority.revoke(db, grant_id='synthetic-grant', operation_id='synthetic-operation') + with patch.object(service.genesis_git_runtime, 'capture') as capture, \ + patch.object(service.genesis_native, 'runtime') as native, \ + patch.object(service.subprocess, 'Popen') as start: + with self.assertRaises(contract.BootstrapError): + service.execute(self.data, operation_id='synthetic-operation', token=self.grant['credential'], headers=self.headers) + capture.assert_not_called(); native.assert_not_called(); start.assert_not_called() + self.assertFalse((self.workspace/'new-repository').exists()) + + @unittest.skipUnless(sys.platform == 'darwin', 'macOS runtime pin only') + def test_failed_readiness_keeps_original_candidate_and_refuses_selection_drift(self): + service.admit(self.data, token=self.grant['credential'], headers=self.headers, value=self.original_request) + with authority.store(self.data, write=True) as db: + row = service._row(db, 'synthetic-operation', self.grant['credential']) + _, scope = authority.authenticate(db, self.grant['credential'], self.headers) + pin = service.genesis_git_runtime.capture(operation_id=scope['operation_id'], host_id=scope['host_id']) + original = {'attempt_id':'b'*32,'step':'PREFLIGHT','state':'STEP_PERMITTED','git_runtime':pin} + row['evidence']['attempts'] = [original] + service._save(db, row, self.grant['credential']) + changed = deepcopy(pin); changed['executable']['sha256'] = '0'*64 + with patch.object(service.genesis_native, 'runtime', return_value=Path(sys.executable)), \ + patch.object(service.genesis_git_runtime, 'capture', return_value=changed) as current_selection, \ + patch.object(service.subprocess, 'Popen') as start: + with self.assertRaisesRegex(contract.BootstrapError, 'GIT_RUNTIME_CHANGED'): + service.execute(self.data, operation_id='synthetic-operation', token=self.grant['credential'], headers=self.headers) + self.assertEqual(current_selection.call_count, 1) # Verification, not replacement followed by verification. + start.assert_not_called() + with authority.store(self.data) as db: + evidence = service._row(db, 'synthetic-operation', self.grant['credential'])['evidence'] + self.assertEqual(evidence['attempts'][0], original) + self.assertNotIn('git_runtime', evidence['known']) + self.assertFalse((self.workspace/'new-repository').exists()) + + @unittest.skipUnless(sys.platform == 'darwin', 'macOS runtime pin only') + def test_late_binary_check_refuses_before_git_start(self): + scope, known, files = self.prepared() + known.update(self._git_initialize(scope, known, files)) + with patch.object(worker, '_git_executable', side_effect=('/fixed-own-git', + contract.BootstrapError('BOOTSTRAP_GIT_RUNTIME_CHANGED',409))), \ + patch.object(worker, '_run_git') as git: + with self.assertRaisesRegex(contract.BootstrapError, 'GIT_RUNTIME_CHANGED'): + worker._git(self.workspace/scope['target_name'], 'status', known=known, scope=scope) + git.assert_not_called() + + @unittest.skipUnless(sys.platform == 'darwin', 'actual macOS pinned runtime check') + def test_late_same_inode_metadata_is_refused_on_execution_descriptor(self): + scope, known, files, metadata = self.failed_original_publication() + original = worker._git_executable + calls = [] + def insert_during_runtime_check(*args, **kwargs): + calls.append(1) + if len(calls) == 2: + (metadata/'unproved-late').write_bytes(b'actual late same-inode bytes') + return original(*args, **kwargs) + with patch.object(worker, '_git_executable', side_effect=insert_during_runtime_check), \ + patch.object(worker, '_run_git') as git: + with self.assertRaisesRegex(contract.BootstrapError, 'OWNERSHIP_UNCERTAIN'): + self._git_initialize(scope, known, files) + git.assert_not_called() + self.assertEqual((metadata/'unproved-late').read_bytes(), b'actual late same-inode bytes') + + @unittest.skipUnless(sys.platform == 'darwin', 'actual macOS pinned runtime check') + def test_late_document_change_is_refused_before_original_empty_init(self): + scope, known, files, metadata = self.failed_original_publication() + original = worker._git_executable + calls = [] + changed = self.workspace/scope['target_name']/next(iter(files)) + def replace_document_during_runtime_check(*args, **kwargs): + calls.append(1) + if len(calls) == 2: changed.write_bytes(b'late original manifest conflict') + return original(*args, **kwargs) + with patch.object(worker, '_git_executable', side_effect=replace_document_during_runtime_check), \ + patch.object(worker, '_run_git') as git: + with self.assertRaises(contract.BootstrapError): + self._git_initialize(scope, known, files) + git.assert_not_called() + self.assertEqual(changed.read_bytes(), b'late original manifest conflict') + self.assertEqual(list(metadata.iterdir()), []) diff --git a/tests/engineering/test_genesis_git_runtime.py b/tests/engineering/test_genesis_git_runtime.py new file mode 100644 index 00000000..b1f1f110 --- /dev/null +++ b/tests/engineering/test_genesis_git_runtime.py @@ -0,0 +1,221 @@ +"""Bounded file/selection drift controls; modeled trust is not native proof.""" +from contextlib import ExitStack +from copy import deepcopy +import io +import os +from pathlib import Path +import stat +import subprocess +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +from engineering_platform import genesis_git_runtime as runtime +from engineering_platform.genesis_contract import BootstrapError + + +class GitRuntimeFileControls(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory(prefix='own-git-runtime-file-') + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + self.toolchain = self.root/'toolchain' + self.git = self.toolchain/'usr/bin/git' + self.git.parent.mkdir(parents=True) + self.git.write_bytes(b'fixed test bytes, NEVER executed\n') + self.git.chmod(0o755) + self.selection = self.root/'selection' + self.selection.symlink_to(self.toolchain) + stack = self.enterContext(ExitStack()) + # This fixture models only the OS trust roots. File descriptors, + # content hashing, no-follow opens and drift remain actual filesystem IO. + original_lstat = Path.lstat + def root_owned_selection(path, *args, **kwargs): + value = original_lstat(path, *args, **kwargs) + if path != self.selection: + return value + fields = {name:getattr(value, name) for name in dir(value) if name.startswith('st_')} + return SimpleNamespace(**{**fields, 'st_uid':0}) + stack.enter_context(patch.object(runtime.sys, 'platform', 'darwin')) + stack.enter_context(patch.object(runtime, 'SELECTION', self.selection)) + stack.enter_context(patch.object(runtime, 'ROOTS', (self.root,))) + stack.enter_context(patch.object(Path, 'lstat', root_owned_selection)) + # World-writable temporary ancestors are not production trust roots. + # Their policy is exercised separately below, not silently admitted. + stack.enter_context(patch.object(runtime, '_ancestors', return_value=[{'fixture':'modeled trusted ancestors'}])) + + def capture(self): + return runtime.capture(operation_id='own-operation', host_id='own-host') + + def verify(self, pin, **kwargs): + return runtime.verify(pin, operation_id='own-operation', host_id='own-host', **kwargs) + + def test_original_bytes_pin_and_same_selection_roundtrip(self): + pin = self.capture() + self.assertEqual(self.verify(pin), str(self.git)) + self.assertEqual(self.verify(pin, selection=True), str(self.git)) + self.assertEqual(pin['executable']['ino'], self.git.stat().st_ino) + + def test_changed_bytes_refuse_without_repinning(self): + pin = self.capture() + self.git.write_bytes(b'changed original executable bytes') + for selection in (False, True): + with self.assertRaisesRegex(BootstrapError, 'GIT_RUNTIME_CHANGED'): + self.verify(pin, selection=selection) + self.assertNotEqual(self.capture()['executable']['sha256'], pin['executable']['sha256']) + + def test_changed_selection_refuses_even_identical_binary_bytes(self): + pin = self.capture() + other = self.root/'other/usr/bin/git' + other.parent.mkdir(parents=True) + other.write_bytes(self.git.read_bytes()); other.chmod(0o755) + self.selection.unlink(); self.selection.symlink_to(other.parents[2]) + with self.assertRaisesRegex(BootstrapError, 'GIT_RUNTIME_CHANGED'): + self.verify(pin, selection=True) + + def test_invalid_or_cross_operation_pin_is_not_a_new_selection_request(self): + pin = self.capture() + bad = [None, {}, {**pin,'format':True}, {**pin,'format':2}, {**pin,'mode':'other'}, + {**pin,'operation_id':'another'}, {**pin,'host_id':'another'}, {**pin,'extra':0}, + {**pin,'executable':None}, {**pin,'ancestors':{}}, {**pin,'selection':None}] + with patch.object(runtime, 'capture') as recapture, patch.object(runtime, '_binary') as binary: + for value in bad: + with self.subTest(value=value), self.assertRaisesRegex(BootstrapError, 'PIN_INVALID'): + self.verify(value, selection=True) + recapture.assert_not_called(); binary.assert_not_called() + + def test_writeable_nonexecutable_fifo_and_hardlink_are_refused(self): + for mode in (0o777, 0o775, 0o644): + self.git.chmod(mode) + with self.subTest(mode=mode), self.assertRaisesRegex(BootstrapError, 'UNTRUSTED'): + self.capture() + self.git.unlink(); os.mkfifo(self.git) + with self.assertRaisesRegex(BootstrapError, 'UNTRUSTED'): self.capture() + self.git.unlink(); self.git.write_bytes(b'never execute'); self.git.chmod(0o755) + os.link(self.git, self.git.with_name('hardlink')) + with self.assertRaisesRegex(BootstrapError, 'UNAVAILABLE'): self.capture() + + def test_descriptor_read_drift_is_rejected(self): + original = runtime.selected_bytes + def change_after_read(*args, **kwargs): + result = original(*args, **kwargs) + self.git.write_bytes(b'real content changed after bounded descriptor read') + return result + with patch.object(runtime, 'selected_bytes', side_effect=change_after_read): + with self.assertRaisesRegex(BootstrapError, 'GIT_RUNTIME_CHANGED'): + self.capture() + + def test_bad_pin_path_or_extra_binary_field_remains_red(self): + pin = self.capture() + for value in (3, '/outside/usr/bin/git', str(self.git.with_name('not-git'))): + bad = deepcopy(pin); bad['executable']['path'] = value + with self.subTest(path=value), self.assertRaises(BootstrapError): self.verify(bad) + bad = deepcopy(pin); bad['executable']['extra'] = 0 + with self.assertRaisesRegex(BootstrapError, 'CHANGED'): self.verify(bad) + + def test_missing_os_selection_and_unsupported_platform_are_explicit(self): + self.selection.unlink() + with self.assertRaisesRegex(BootstrapError, 'UNAVAILABLE'): self.capture() + with patch.object(runtime.sys, 'platform', 'other'): + with self.assertRaisesRegex(BootstrapError, 'MODE_UNSUPPORTED'): self.capture() + + +class AncestorTrustControls(unittest.TestCase): + def test_root_group_write_requires_explicit_privileged_darwin_group(self): + for group in (20, 99, 501): + info = SimpleNamespace(st_uid=0, st_gid=group, st_mode=stat.S_IFDIR|0o775, st_dev=1, st_ino=3) + with self.subTest(group=group), patch.object(Path, 'lstat', return_value=info): + with self.assertRaisesRegex(BootstrapError, 'UNTRUSTED'): + runtime._ancestors(Path('/Applications/fixture/git')) + for group in (0, 80): + info = SimpleNamespace(st_uid=0, st_gid=group, st_mode=stat.S_IFDIR|0o775, st_dev=1, st_ino=3) + with self.subTest(group=group), patch.object(Path, 'lstat', return_value=info): + self.assertTrue(runtime._ancestors(Path('/Applications/fixture/git'))) + # A root-owned read-only ancestor need not delegate writes to its group. + info = SimpleNamespace(st_uid=0, st_gid=20, st_mode=stat.S_IFDIR|0o755, st_dev=1, st_ino=3) + with patch.object(Path, 'lstat', return_value=info): + self.assertTrue(runtime._ancestors(Path('/Applications/fixture/git'))) + # Even admin-group write is not allowed on a non-root-owned ancestor. + info = SimpleNamespace(st_uid=501, st_gid=80, st_mode=stat.S_IFDIR|0o775, st_dev=1, st_ino=3) + with patch.object(runtime.os, 'geteuid', return_value=501), patch.object(Path, 'lstat', return_value=info): + with self.assertRaisesRegex(BootstrapError, 'UNTRUSTED'): + runtime._ancestors(Path('/Applications/fixture/git')) + + def test_foreign_owner_other_writes_and_nondirectory_are_rejected(self): + actual = runtime._ancestors + for owner, mode in ((os.geteuid()+100000, 0o40755), (os.geteuid(), 0o40777), + (0, 0o40777), (0, 0o100755)): + info = SimpleNamespace(st_uid=owner, st_gid=20, st_mode=mode, st_dev=1, st_ino=3) + with self.subTest(owner=owner, mode=mode), patch.object(Path, 'lstat', return_value=info): + with self.assertRaisesRegex(BootstrapError, 'UNTRUSTED'): + actual(Path('/Applications/fixture/git')) + info = SimpleNamespace(st_uid=0, st_gid=80, st_mode=stat.S_IFDIR|0o775, st_dev=1, st_ino=3) + with patch.object(Path, 'lstat', return_value=info): + self.assertTrue(actual(Path('/Applications/fixture/git'))) + + +class OriginalGitLogControls(unittest.TestCase): + def test_timeout_keeps_received_bytes_and_same_primary(self): + from engineering_platform import genesis_worker as worker + error = subprocess.TimeoutExpired('fixed own fixture', 5, output=b'original-out', stderr=b'original-error') + sink = io.BytesIO() + with patch.object(worker.subprocess, 'run', side_effect=error), \ + patch.object(worker.sys, 'stderr', SimpleNamespace(buffer=sink)): + with self.assertRaises(subprocess.TimeoutExpired) as caught: + worker._run_git(('fixed-own-fixture','--version'), 42) + self.assertIs(caught.exception, error) + self.assertIn(b'original-out', sink.getvalue()) + self.assertIn(b'original-error', sink.getvalue()) + + def test_log_failures_attempt_both_streams_without_masking_timeout(self): + from engineering_platform import genesis_worker as worker + error = subprocess.TimeoutExpired('fixed own fixture', 5, output=b'out', stderr=b'err') + class BrokenSink: + def __init__(self): self.writes = [] + def write(self, raw): + self.writes.append(raw) + raise OSError('own bounded log failure') + sink = BrokenSink() + with patch.object(worker.subprocess, 'run', side_effect=error), \ + patch.object(worker.sys, 'stderr', SimpleNamespace(buffer=sink)): + with self.assertRaises(subprocess.TimeoutExpired) as caught: + worker._run_git(('fixed-own-fixture','--version'), 42) + self.assertIs(caught.exception, error) + self.assertEqual(len(sink.writes), 2) + self.assertIsInstance(error.__cause__, ExceptionGroup) + self.assertEqual(len(error.__cause__.exceptions), 2) + + def test_success_stderr_is_retained_and_oversized_output_stays_red(self): + from engineering_platform import genesis_worker as worker + sink = io.BytesIO() + with patch.object(worker.subprocess, 'run', return_value=SimpleNamespace(returncode=0,stdout=b'x'*262145,stderr=b'original-success-stderr')), \ + patch.object(worker.sys, 'stderr', SimpleNamespace(buffer=sink)): + with self.assertRaisesRegex(BootstrapError, 'GIT_OUTPUT_LIMIT'): + worker._run_git(('fixed-own-fixture','--version'), 42) + self.assertIn(b'original-success-stderr', sink.getvalue()) + self.assertLess(len(sink.getvalue()), 262144+65536) + + def test_nonzero_or_stdout_limit_with_log_failure_keeps_primary_and_exit_status(self): + from engineering_platform import genesis_worker as worker + class BrokenSink: + def write(self, raw): raise OSError('own exact log failure') + for code, raw, expected in ((7,b'out','LOCAL_GIT_FAILED'),(0,b'x'*262145,'GIT_OUTPUT_LIMIT')): + with self.subTest(code=code, expected=expected), \ + patch.object(worker.subprocess, 'run', return_value=SimpleNamespace(returncode=code,stdout=raw,stderr=b'original-error')), \ + patch.object(worker.sys, 'stderr', SimpleNamespace(buffer=BrokenSink())): + with self.assertRaisesRegex(BootstrapError, expected) as caught: + worker._run_git(('fixed-own-fixture','--version'), 42) + self.assertIsInstance(caught.exception.__cause__, ExceptionGroup) + self.assertTrue(any(f'Original Git exit={code},' in note for note in caught.exception.__notes__)) + + def test_successful_stderr_overflow_is_explicitly_red(self): + from engineering_platform import genesis_worker as worker + sink = io.BytesIO() + with patch.object(worker.subprocess, 'run', return_value=SimpleNamespace(returncode=0,stdout=b'git version fixture\n',stderr=b'x'*65537)), \ + patch.object(worker.sys, 'stderr', SimpleNamespace(buffer=sink)): + with self.assertRaisesRegex(BootstrapError, 'GIT_ERROR_OUTPUT_LIMIT') as caught: + worker._run_git(('fixed-own-fixture','--version'), 42) + self.assertIsInstance(caught.exception.__cause__, ExceptionGroup) + self.assertTrue(any('stderr_bytes=65537' in note for note in caught.exception.__notes__)) + self.assertLess(len(sink.getvalue()), 65536+256) diff --git a/tests/engineering/test_genesis_process_group_observer.py b/tests/engineering/test_genesis_process_group_observer.py new file mode 100644 index 00000000..e3429044 --- /dev/null +++ b/tests/engineering/test_genesis_process_group_observer.py @@ -0,0 +1,92 @@ +"""Independent real OS observations do not grant effect or signal authority.""" +import importlib.util +import ctypes +import errno +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import Mock, patch + +SPEC = importlib.util.spec_from_file_location("group_observer", Path(__file__).resolve().parents[2] / + "tools/qualification/genesis_process_group_observer.py") +observer = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(observer) + + +class ObserverContractTests(unittest.TestCase): + def test_libproc_failure_is_uncertain_and_ps_failure_retains_facts(self): + def denied(*args): + ctypes.set_errno(errno.EPERM) + return 0 + library = SimpleNamespace(proc_pidinfo=Mock(), proc_listpids=Mock(side_effect=denied), proc_pidpath=Mock()) + with patch.object(observer.sys, "platform", "darwin"), patch.object(observer.ctypes, "CDLL", return_value=library), \ + patch.object(observer.subprocess, "run", side_effect=subprocess.TimeoutExpired("ps", 2)), \ + patch.object(observer.os, "killpg", side_effect=PermissionError(errno.EPERM, "denied")): + facts = observer.group_facts(100) + self.assertEqual(facts["list_errno"], errno.EPERM) + self.assertFalse(facts["inventory_complete"]) + self.assertEqual(facts["members"], []) + self.assertEqual(facts["ps_error"], "TimeoutExpired") + self.assertIsNone(facts["ps_members"]) + self.assertEqual(facts["signal_zero"], "UNCERTAIN") + + def test_original_failure_object_and_result_are_preserved(self): + error = RuntimeError("original owning failure") + def refused(group): + raise error + with tempfile.TemporaryDirectory() as temporary: + path = Path(temporary) / "observations" + service = SimpleNamespace(_require_group_stopped=refused) + observer.install_observer(service, path) + with self.assertRaises(RuntimeError) as found: + service._require_group_stopped(-1) + self.assertIs(found.exception, error) + event = json.loads(path.read_text()) + self.assertEqual(event["detector"], "FAIL") + self.assertEqual(event["observation_error"], "ValueError") + self.assertEqual(path.stat().st_mode & 0o777, 0o600) + service = SimpleNamespace(_require_group_stopped=lambda group: "original result") + observer.install_observer(service, path) + self.assertEqual(service._require_group_stopped(1), "original result") + + def test_observer_failure_cannot_erase_primary(self): + error = RuntimeError("primary") + def refused(group): + raise error + service = SimpleNamespace(_require_group_stopped=refused) + with tempfile.TemporaryDirectory() as temporary: + observer.install_observer(service, Path(temporary) / "missing" / "log") + with self.assertRaises(RuntimeError) as found: + service._require_group_stopped(-1) + self.assertIs(found.exception, error) + self.assertIn("could not persist", error.__notes__[0]) + + @unittest.skipUnless(sys.platform == "darwin", "Actual Darwin libproc qualification") + def test_actual_live_own_group_then_waited_absence(self): + child = subprocess.Popen((sys.executable, "-I", "-c", "import time;time.sleep(30)"), start_new_session=True) + try: + self.assertEqual(os.getpgid(child.pid), child.pid) + facts = observer.group_facts(child.pid) + self.assertEqual(facts["signal_zero"], "PRESENT") + self.assertEqual(facts["ps_returncode"], 0) + member = next(row for row in facts["members"] if row["pid"] == child.pid) + self.assertTrue(member["identity_stable"]) + self.assertEqual(member["samples"][0]["pgid"], child.pid) + self.assertEqual(member["samples"][0]["session"], child.pid) + finally: + # Direct, unreaped Popen child only; no PID/group-only kill. + child.terminate() + child.wait(timeout=5) + gone = observer.group_facts(child.pid) + self.assertEqual(gone["signal_zero"], "ABSENT_ESRCH") + self.assertFalse(gone["members"]) + self.assertFalse(gone["ps_members"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/qualification/fixtures/genesis-process-group-evidence-recipient.pem b/tools/qualification/fixtures/genesis-process-group-evidence-recipient.pem new file mode 100644 index 00000000..1a95b244 --- /dev/null +++ b/tools/qualification/fixtures/genesis-process-group-evidence-recipient.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEIDCCAoigAwIBAgIUA2Thosiq1msUknJVuNC7HQqGjjwwDQYJKoZIhvcNAQEL +BQAwMDEuMCwGA1UEAwwlTDIgb3duaW5nIEdlbmVzaXMgZGlhZ25vc3RpYyBldmlk +ZW5jZTAeFw0yNjEwMTkwNzE4MzVaFw0yNzAxMTcwNzE4MzVaMDAxLjAsBgNVBAMM +JUwyIG93bmluZyBHZW5lc2lzIGRpYWdub3N0aWMgZXZpZGVuY2UwggGiMA0GCSqG +SIb3DQEBAQUAA4IBjwAwggGKAoIBgQCv9uhkVuYfsgwSeJNvyjTf4BSByz76m73Y +wbUu8oIb5OF2NjZZv1c9/au0XpfSBQTguy+AHnenVQyR+7jnAr10z6ptkVbL7/u1 +DVIGLI5TaK4XTVnzKj3h8TaIbKZYUTsYzWIV29zOaCTCYfOcQZh/tPOLT5WhlepW +5+U4oqtUAB76AsEljYM3kDokTAE+2Otg827d1Hyrc3m96axXGiouiVUYsAPTWxod +yNByHhfE7qBS4dhq5U+Cip96zKyF0ciVxlYTp3JipaZQLEXTF8V0pVBI/6nD4XvQ +aJAoNr3oO1yKJ0tRqFq6fHDg6Q6SwJE+tWDP3Y4+v9jrJHPOrnWQ1kXnBRlrYDw1 +74Daa/CX7jqSbJsjzO+aKi4atI4HEMiTZB5IGGs8FZ4bSswQsDPQ6+FLjrHkiIPs +GKsMTr4fVEwsjKY6NtsAkRmeq2YvfHn6MZ71D1CIzhI7RUGgsiwlhNhOcOdTrpw0 +Thi5cYi/5x0vUXtXeztJl3Jwepn18w8CAwEAAaMyMDAwHQYDVR0OBBYEFPAGAKwN +Mpf+z7mMrDdA253EhNaDMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggGBAGhRJ45exPFwdJTJy4I9m1RCsNQjwCJvNj9hikTYFp0jZ0442cYjkHaPhAjw +2TUu5st8IoQYujMXET29ofkruKOWwjcM0BTdmxwodbDfFDD2UUmh/QSCMWZymStk +yHWlO0GBUmccaT7Vm7rbe4UeYqFMa7vrAsS44+ouDlcRr7sB2WGvjGJTeCIk24EZ +sIOGaucq5/0dtVi1LYCazwfIFxerqbRZj59eXwqYwSQCaY8ouhVT0dZjEh2dJE3s +aIe87MU58B2MycxWMPHjhsWOZTSSue/sNIqBTTxGtJzo53otH3OWsPu9uI3CsDFQ +qsofzTpOSSu1gE5l7bVz02vCl/G5qrdOXHcJsNJRSDNlfvsT7Z3c2OicviOTbtwB +gcT4MdNGuAQCqyT+8ya7J+M4zsA18zl4rK80gKDDPpoyG6SCx2PTdY6KueRWhdEI +JnJTVsDSeBDISAiFwNmO6jeB19JFPItA440o6T0RsyA+wHwpBtMm2Yb5Pb50CgNc +ORZO+g== +-----END CERTIFICATE----- diff --git a/tools/qualification/genesis_git_runtime_probe.py b/tools/qualification/genesis_git_runtime_probe.py new file mode 100644 index 00000000..85adbd10 --- /dev/null +++ b/tools/qualification/genesis_git_runtime_probe.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +"""Read-only native comparison of Apple's Git shim and its selected real Git. + +No Git repository, grant, submission, commit or B8R record is created. Neither +the sandbox policy nor the owning process-group detector is changed. Original +private process images/fd types and output are encrypted before publication. +""" +from __future__ import annotations + +import argparse +import ctypes +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import stat +import subprocess +import sys +import tarfile +import time + +_spec = importlib.util.spec_from_file_location('own_group_observer', Path(__file__).with_name('genesis_process_group_observer.py')) +_observer = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_observer) +append_private, group_facts = _observer.append_private, _observer.group_facts + + +WORKER = '''import json,os,subprocess,sys +from engineering_platform.effect_workspace import child_environment +fd=os.open(sys.argv[2],os.O_RDONLY|os.O_DIRECTORY) +try: + try: + p=subprocess.run((sys.argv[1],'--version'),preexec_fn=lambda:os.fchdir(fd),pass_fds=(fd,),env=child_environment(),input=b'',stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=5,check=False) + result={'returncode':p.returncode,'stdout_hex':p.stdout.hex(),'stderr_hex':p.stderr.hex()} + except subprocess.TimeoutExpired as error:result={'error_type':type(error).__name__,'stdout_hex':(error.stdout or b'').hex(),'stderr_hex':(error.stderr or b'').hex()} + except subprocess.SubprocessError as error:result={'error_type':type(error).__name__} + print(json.dumps(result),flush=True) +finally:os.close(fd) +''' + + +def private_images(group: int, path: Path) -> None: + """Birth guarded image paths/fd types, encrypted only; never public argv.""" + before = group_facts(group) + lib = ctypes.CDLL('/usr/lib/libproc.dylib', use_errno=True) + lib.proc_pidpath.argtypes = (ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32) + lib.proc_pidpath.restype = ctypes.c_int + lib.proc_pidinfo.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_int) + lib.proc_pidinfo.restype = ctypes.c_int + rows = [] + class FdInfo(ctypes.Structure): + _fields_ = [('fd', ctypes.c_int32), ('kind', ctypes.c_uint32)] + for member in before['members']: + if not member['identity_stable']: + continue + pid = member['pid'] + buffer = ctypes.create_string_buffer(4096) + count = lib.proc_pidpath(pid, buffer, len(buffer)) + fds = (FdInfo * 256)() + ctypes.set_errno(0) + size = lib.proc_pidinfo(pid, 1, 0, fds, ctypes.sizeof(fds)) # PROC_PIDLISTFDS + fd_errno = ctypes.get_errno() + rows.append({'pid': pid, 'start': member['samples'][0]['start'], + 'image': os.fsdecode(buffer.value) if count > 0 else None, + 'fd_bytes': size, 'fd_errno': fd_errno, + 'fd_inventory_complete':fd_errno == 0 and 0 < size < ctypes.sizeof(fds) and size % ctypes.sizeof(FdInfo) == 0, + 'fds': [{'fd': item.fd, 'kind': item.kind} for item in fds[:max(0, min(size, ctypes.sizeof(fds))) // ctypes.sizeof(FdInfo)]]}) + after = group_facts(group) + after_births = {m['pid']: m['samples'][0]['start'] for m in after['members'] if m['identity_stable']} + for row in rows: + row['birth_guard_after'] = after_births.get(row['pid']) == row['start'] + append_private(path, {'at_ns': time.monotonic_ns(), 'group': group, 'before': before, 'private_images': rows, 'after': after}) + + +def one(args, root: Path, label: str, executable: str) -> dict: + from engineering_platform import genesis_native, genesis_service + from engineering_platform.genesis_contract import BootstrapError + from engineering_platform.provider_process_identity import capture_process_identity, verify_process_identity + native = genesis_native.runtime() + command = genesis_native.command(native=native, anchor=root/'a', target=root/'r', writable=False, + readable=(Path(genesis_service.__file__).resolve().parent.parent,), + child=(str(args.python), '-I', '-c', WORKER, executable, str(root/'r')), profile_id='ep-genesis-git-'+label) + deadline = time.monotonic()+30 + stderr = bytearray() + records = bytearray() + record = {'label': label} + process = subprocess.Popen(command, cwd=root/'a', env=genesis_native.environment(native), + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True) + original_identity = capture_process_identity(process.pid, process.pid) + def drain(): + os.set_blocking(process.stderr.fileno(), False) + while True: + try: chunk = os.read(process.stderr.fileno(), 65536) + except BlockingIOError: return + if not chunk: return + room = 262144-len(stderr) + stderr.extend(chunk[:room]) + if len(chunk) > room: raise RuntimeError('Bounded diagnostic stderr exceeded') + def drain_tail(): + os.set_blocking(process.stdout.fileno(), False) + while True: + try: chunk = os.read(process.stdout.fileno(), 65536) + except BlockingIOError: return + if not chunk: return + room = 262144-len(records) + records.extend(chunk[:room]) + if len(chunk) > room: raise RuntimeError('Bounded diagnostic stdout exceeded') + try: + raw = genesis_service._line(process.stdout, deadline, guard=drain, records=records) + record['worker'] = json.loads(raw) + protocol_size = len(records) + while process.poll() is None: + drain() + drain_tail() + if time.monotonic() >= deadline: raise TimeoutError('Original30s native deadline') + time.sleep(.01) + process.wait(timeout=.01) + drain() + drain_tail() + if len(records) != protocol_size: raise RuntimeError('Unexpected diagnostic protocol output') + record['native_returncode'] = process.returncode + try: + genesis_service._require_group_stopped(process.pid) + except BootstrapError as error: + record['original_group_check'] = error.code + record['failure_facts'] = group_facts(process.pid) + private_images(process.pid, args.output/'private'/('images-'+label+'.jsonl')) + else: + record['original_group_check'] = 'ABSENT_ESRCH' + # Observation within the SAME original deadline; no effect/step is + # advanced and an earlier original refusal is never relabelled PASS. + while True: + drain() + drain_tail() + if len(records) != protocol_size: raise RuntimeError('Unexpected diagnostic protocol output') + try: os.killpg(process.pid, 0) + except ProcessLookupError: + record['finally_absent'] = True + break + if time.monotonic() >= deadline: + record['finally_absent'] = False + break + time.sleep(.02) + except Exception as error: + record['probe_error_type'] = type(error).__name__ + record['primary_error'] = {'type':type(error).__name__,'detail':str(error)} + finally: + cleanup_errors = [] + def retained(error): + cleanup_errors.append({'type':type(error).__name__,'detail':str(error)}) + try: + if process.poll() is None: + if original_identity is not None and verify_process_identity(original_identity) == 'MATCH': + process.terminate() # Only this positively identified direct child. + try: process.wait(timeout=2) + except subprocess.TimeoutExpired: record['direct_child_stop_uncertain'] = True + else: record['direct_child_stop_uncertain'] = True + except Exception as error: + retained(error) + try: drain() + except Exception as error: retained(error) + try: drain_tail() + except Exception as error: retained(error) + if 'protocol_size' in locals() and len(records) != protocol_size: + retained(RuntimeError('Unexpected diagnostic protocol output')) + for stream in (process.stdout, process.stderr): + try: stream.close() + except Exception as error: retained(error) + for suffix,data in (('stdout',records),('stderr',stderr)): + try: + path=args.output/'private'/(label+'.'+suffix) + path.write_bytes(data) + path.chmod(0o600) + except Exception as error: + retained(error) + record['cleanup_errors'] = cleanup_errors + record['control_integrity'] = ('FAIL' if record.get('primary_error') or cleanup_errors + or record.get('direct_child_stop_uncertain') or not record.get('finally_absent') else 'PASS') + append_private(args.output/'private'/'original-controls.jsonl', record) + return record + + +def selected_git(args): + # Read the existing root-owned OS choice without starting xcrun, + # xcodebuild or any other selector/cache-warming helper process. + selection=Path('/var/db/xcode_select_link') + selection_info=selection.lstat() + if selection_info.st_uid != 0 or not stat.S_ISLNK(selection_info.st_mode): + raise RuntimeError('Supported root-owned OS toolchain selection required') + selection_text=str(selection.readlink()) + selected = (selection/'usr/bin/git').resolve(strict=True) + if not any(selected.is_relative_to(prefix) for prefix in (Path('/Applications'), Path('/Library/Developer'))): + raise RuntimeError('Unexpected selected toolchain placement') + trusted_owners = {0, os.getuid()} + for node in selected.parents: + info = node.stat() + if (info.st_uid not in trusted_owners or + info.st_mode & (0o002 if info.st_uid == 0 else 0o022)): + raise RuntimeError('Untrusted selected toolchain parent') + info = selected.stat() + if (info.st_uid not in trusted_owners or info.st_mode & 0o022 or not stat.S_ISREG(info.st_mode) + or not os.access(selected, os.X_OK) or selected.name != 'git'): + raise RuntimeError('Actual root-or-current-owner non-shim Git required') + selected_digest=hashlib.sha256(selected.read_bytes()).hexdigest() + selected_identity=tuple(getattr(info,key) for key in ('st_dev','st_ino','st_uid','st_gid','st_mode','st_size','st_mtime_ns','st_ctime_ns')) + append_private(args.output/'private'/'selected-git.jsonl', {'path':str(selected),'device':info.st_dev,'inode':info.st_ino, + 'sha256':selected_digest, 'mode':stat.S_IMODE(info.st_mode), + 'selection_device':selection_info.st_dev,'selection_inode':selection_info.st_ino,'selection_text':selection_text}) + return selected, selection, selection_info, selection_text, selected_identity, selected_digest + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--selection-inventory-only', action='store_true') + parser.add_argument('--order', choices=('shim-first','direct-first'), default='shim-first') + for name in ('python', 'native', 'output', 'recipient'): + parser.add_argument('--'+name, type=Path, required=True) + args = parser.parse_args() + for name in ('python', 'native', 'output', 'recipient'): + setattr(args, name, getattr(args, name).absolute()) + if sys.platform != 'darwin': raise RuntimeError('Owning Darwin host required') + os.environ['EP_MANAGED_CODEX_CLI_PREFIX'] = str(args.native) + args.output.mkdir(mode=0o700) + (args.output/'private').mkdir(mode=0o700) + (args.output/'public').mkdir(mode=0o700) + controls = [] + lookup_error = None + try: + if args.selection_inventory_only: + # Filesystem observations only. Do not start native/Git/selector + # processes, change permissions or bypass any execution guard. + selection = Path('/var/db/xcode_select_link') + selected = (selection/'usr/bin/git').resolve(strict=True) + for node in (selection, selected, *selected.parents): + info = node.lstat() + append_private(args.output/'private'/'selection-inventory.jsonl', + {'path':str(node), 'device':info.st_dev,'inode':info.st_ino, + 'uid':info.st_uid,'gid':info.st_gid,'mode':stat.S_IMODE(info.st_mode), + 'symlink':str(node.readlink()) if node.is_symlink() else None, + 'current_uid':os.getuid(), + 'sha256':hashlib.sha256(node.read_bytes()).hexdigest() if node == selected else None}) + else: + root = Path('/private/tmp/pgd1') + root.mkdir(mode=0o700) + (root/'a').mkdir(mode=0o700) + (root/'r').mkdir(mode=0o700) + append_private(args.output/'private'/'root-identity.jsonl', {'device':root.stat().st_dev,'inode':root.stat().st_ino}) + if args.order == 'direct-first': + selected, selection, selection_info, selection_text, selected_identity, selected_digest = selected_git(args) + controls.append(one(args, root, 'selected-direct-cold', str(selected))) + else: + controls.append(one(args, root, 'system-shim', 'git')) + if controls and controls[0].get('control_integrity') == 'PASS': + if args.order == 'shim-first': + selected, selection, selection_info, selection_text, selected_identity, selected_digest = selected_git(args) + controls.append(one(args, root, 'system-shim-after-direct' if args.order == 'direct-first' else 'system-shim-after-selection-read', 'git')) + if controls[1].get('control_integrity') == 'PASS': + controls.append(one(args, root, 'selected-direct', str(selected))) + fresh=selection.lstat() + selected_after=selected.stat() + after_identity=tuple(getattr(selected_after,key) for key in ('st_dev','st_ino','st_uid','st_gid','st_mode','st_size','st_mtime_ns','st_ctime_ns')) + if ((fresh.st_dev,fresh.st_ino)!=(selection_info.st_dev,selection_info.st_ino) + or str(selection.readlink())!=selection_text or after_identity!=selected_identity + or hashlib.sha256(selected.read_bytes()).hexdigest()!=selected_digest): + raise RuntimeError('Selected OS toolchain changed during comparison') + except Exception as error: + lookup_error = type(error).__name__ + append_private(args.output/'private'/'control-errors.jsonl', {'type':lookup_error,'detail':str(error)}) + public_controls = [] + for row in controls: + public_controls.append({key:row[key] for key in ('label','native_returncode','original_group_check','failure_facts', + 'finally_absent','probe_error_type','direct_child_stop_uncertain','control_integrity') if key in row}) + public_controls[-1]['git_returncode'] = row.get('worker',{}).get('returncode') + public_controls[-1]['git_error_type'] = row.get('worker',{}).get('error_type') + diagnosis = (args.order == 'shim-first' and len(controls)==3 and not lookup_error + and all(row.get('original_group_check')=='BOOTSTRAP_HOST_GROUP_ACTIVE_OR_UNCERTAIN' for row in controls[:2]) + and controls[2].get('original_group_check')=='ABSENT_ESRCH' and controls[2].get('native_returncode')==0 + and controls[2].get('worker',{}).get('returncode')==0 + and all(row.get('control_integrity')=='PASS' for row in controls)) + reverse_diagnosis = (args.order == 'direct-first' and len(controls)==3 and not lookup_error + and all(row.get('original_group_check')=='ABSENT_ESRCH' and row.get('native_returncode')==0 + and row.get('worker',{}).get('returncode')==0 for row in (controls[0],controls[2])) + and controls[1].get('original_group_check')=='BOOTSTRAP_HOST_GROUP_ACTIVE_OR_UNCERTAIN' + and controls[1].get('worker',{}).get('error_type')=='TimeoutExpired' + and all(row.get('control_integrity')=='PASS' for row in controls)) + summary = {'result':('METADATA_ONLY_NO_EXECUTION' if args.selection_inventory_only and not lookup_error else + 'DIRECT_COLD_SUCCESS_SHIM_GROUP_FAILURE' if reverse_diagnosis else + 'SHIM_FAILURE_PERSISTS_SELECTED_DIRECT_SUCCEEDS' if diagnosis else 'NO_CLOSING_DIAGNOSIS'), + 'order':args.order, + 'controls':public_controls,'driver_error_type':lookup_error, + 'meaning':'Read-only native toolchain diagnosis; no productrepair/Genesis qualification PASS'} + archive = args.output/'private'/'original.tar.gz' + with tarfile.open(archive,'w:gz') as saved: + for path in sorted((args.output/'private').iterdir()): + if path != archive: saved.add(path, arcname=path.name) + encrypted = args.output/'public'/'primary-evidence.cms' + subprocess.run(('openssl','cms','-encrypt','-binary','-aes-256-cbc','-in',str(archive),'-outform','DER','-out',str(encrypted),str(args.recipient)),check=True,capture_output=True) + summary['private_archive_sha256'] = hashlib.sha256(archive.read_bytes()).hexdigest() + summary['encrypted_archive_sha256'] = hashlib.sha256(encrypted.read_bytes()).hexdigest() + (args.output/'public'/'summary.json').write_text(json.dumps(summary,indent=2)+'\n') + print(json.dumps({'result':summary['result'],'controls':len(controls)})) + return 0 if diagnosis or reverse_diagnosis or args.selection_inventory_only and not lookup_error else 1 + + +if __name__ == '__main__': raise SystemExit(main()) diff --git a/tools/qualification/genesis_local_bootstrap_http.py b/tools/qualification/genesis_local_bootstrap_http.py index 596defb8..210a0850 100644 --- a/tools/qualification/genesis_local_bootstrap_http.py +++ b/tools/qualification/genesis_local_bootstrap_http.py @@ -82,7 +82,7 @@ def __init__(self, args, name, index, schema): self.evidence = args.output/name self.evidence.mkdir(mode=0o700) write_private(self.root/'.qualification-owner.json', { - 'assignment': 'L2-GENESIS-LOCAL-BOOTSTRAP-V1-20261016', 'source': args.source, + 'assignment': getattr(args, 'assignment', 'L2-GENESIS-LOCAL-BOOTSTRAP-V1-20261016'), 'source': args.source, 'uid': os.getuid(), 'identity': [self.root.stat().st_dev, self.root.stat().st_ino]}) self.env = {k: v for k, v in os.environ.items() if k not in {'PYTHONPATH', 'PYTHONHOME'}} self.env.update(PATH=str(args.python.parent)+':'+str(args.native/'bin')+':/usr/bin:/bin', diff --git a/tools/qualification/genesis_process_group_host_probe.py b/tools/qualification/genesis_process_group_host_probe.py new file mode 100644 index 00000000..d70fcafc --- /dev/null +++ b/tools/qualification/genesis_process_group_host_probe.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""One installed owning Genesis chain with private, read-only group facts. + +Only the whitelisted public summary and encrypted primary evidence may be +uploaded. The ordinary owning detector and private effect scope are unchanged. +""" +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import platform +import subprocess +import sys +import tarfile +import traceback +import zipfile + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + for name in ("python", "native", "bundle", "validator", "wheel", "output", "recipient"): + parser.add_argument("--" + name, type=Path, required=True) + parser.add_argument("--source", required=True) + parser.add_argument("--assignment", default="L2-GENESIS-MACOS-PROCESS-GROUP-CLOSURE-V1-20261019") + parser.add_argument("--wheel-sha256", required=True) + parser.add_argument("--root-prefix", required=True) + parser.add_argument("--case", choices=("concurrency", "before-register", "lost-success"), default="before-register") + args = parser.parse_args() + # Case executes installed commands with cwd set to its new synthetic root. + # Inherit no relative PATH/prefix/fixture interpretation across that boundary. + for name in ("python", "native", "bundle", "validator", "wheel", "output", "recipient"): + setattr(args, name, getattr(args, name).absolute()) + if sys.platform != "darwin" or not args.root_prefix.isalnum() or len(args.root_prefix) > 4: + raise ValueError("Explicit bounded owning Darwin placement required") + if len(args.source) != 40 or any(c not in "0123456789abcdef" for c in args.source): + raise ValueError("Exact source pin required") + if hashlib.sha256(args.wheel.read_bytes()).hexdigest() != args.wheel_sha256: + raise ValueError("Wrong original wheel") + args.output.mkdir(mode=0o700) + public = args.output / "public" + public.mkdir(mode=0o700) + private = args.output / "private" + private.mkdir(mode=0o700) + args.output = private + own_tools = Path(__file__).resolve().parent + spec = importlib.util.spec_from_file_location("own_genesis_probe", own_tools / "genesis_local_bootstrap_http.py") + owning = importlib.util.module_from_spec(spec) + spec.loader.exec_module(owning) + # Copy the observer into the synthetic fault launcher; its imports still + # select the ordinary installed package, not a checkout/PYTHONPATH overlay. + observer_source = (own_tools / "genesis_process_group_observer.py").read_text() + observer_source = observer_source.replace("from __future__ import annotations\n", "") + prelude = observer_source + "\nfrom engineering_platform import genesis_service as _observed_service\n" + prelude += "install_observer(_observed_service, Path(sys.argv[3]) / 'own-group-observations.jsonl')\n" + owning.FAULT_LAUNCHER = prelude + owning.FAULT_LAUNCHER + from jsonschema import Draft202012Validator, FormatChecker + import engineering_platform + installed = Path(engineering_platform.__file__).resolve().parent + if installed.is_relative_to(own_tools.parents[1]): + raise ValueError("Noneditable installation outside checkout required") + with zipfile.ZipFile(args.wheel) as wheel: + files = [name for name in wheel.namelist() if name.startswith("engineering_platform/") and not name.endswith("/")] + for name in files: + if (installed.parent / name).read_bytes() != wheel.read(name): + raise ValueError("Installed product differs from original wheel") + schema = Draft202012Validator(json.loads(wheel.read("engineering_platform/schemas/genesis-local-bootstrap-v1.schema.json")), + format_checker=FormatChecker()) + case = None + primary = cleanup = None + try: + case = owning.Case(args, args.case, 0, schema) + # Retain only this finite synthetic owner-issued credential privately. + # An unexpected real failure must not destroy the possibility of an + # explicitly authorized original-operation resume after Server restart. + owning.write_private(case.root / "original-grant.private.json", case.grant) + case.run() + case.record["state"] = "PASS" + except BaseException as error: + primary = error + if case: + case.record.update(state="FAILED", failure=traceback.format_exc()) + finally: + if case: + try: + case.finish() + except BaseException as error: + cleanup = error + owning.write_private(private / "probe-errors.json", { + "primary": None if primary is None else { + "type": type(primary).__name__, + "traceback": "".join(traceback.format_exception(primary)), + }, + "cleanup": None if cleanup is None else { + "type": type(cleanup).__name__, + "traceback": "".join(traceback.format_exception(cleanup)), + }, + }) + observations = [] + if case and (case.data / "own-group-observations.jsonl").is_file(): + observations = [json.loads(line) for line in (case.data / "own-group-observations.jsonl").read_text().splitlines()] + # This file has no tokens, paths, command lines, argv or environment data. + summary = {"source": args.source, "wheel_sha256": args.wheel_sha256, + "installed_payload_files": len(files), "system": platform.system(), "release": platform.release(), + "macos": platform.mac_ver()[0], "machine": platform.machine(), "case": args.case, + "result": "PASS" if case and case.record.get("state") == "PASS" and not primary and not cleanup else "FAIL", + "primary_type": None if primary is None else type(primary).__name__, + "cleanup_type": None if cleanup is None else type(cleanup).__name__, + "group_observations": observations, + "limitation": "Instrumented owning chain; not full Forge R57 or final-main qualification"} + if not observations: + summary["result"] = "FAIL" + summary["diagnostic_error"] = "NO_GROUP_OBSERVATIONS" + # Preserve private original receipts, stdout/stderr, database, Git and + # exact observation records after the attempted actual process stop. + archive = private / "primary.tar.gz" + with tarfile.open(archive, "w:gz") as saved: + saved.add(private / "probe-errors.json", arcname="probe-errors.json") + if case: + saved.add(case.root, arcname="own-synthetic-root", recursive=True) + saved.add(case.evidence, arcname="own-original-qualification", recursive=True) + encrypted = public / "primary-evidence.cms" + subprocess.run(("openssl", "cms", "-encrypt", "-binary", "-aes-256-cbc", "-in", str(archive), + "-outform", "DER", "-out", str(encrypted), str(args.recipient)), check=True, capture_output=True) + summary["private_archive_sha256"] = hashlib.sha256(archive.read_bytes()).hexdigest() + summary["encrypted_archive_sha256"] = hashlib.sha256(encrypted.read_bytes()).hexdigest() + (public / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") + print(json.dumps({"result": summary["result"], "case": args.case, + "observed_checks": len(observations), "failed_checks": sum(row["detector"] == "FAIL" for row in observations)})) + # No failure suppression, including original chain/cleanup failures. + return 0 if summary["result"] == "PASS" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/qualification/genesis_process_group_observer.py b/tools/qualification/genesis_process_group_observer.py new file mode 100644 index 00000000..36deaa63 --- /dev/null +++ b/tools/qualification/genesis_process_group_observer.py @@ -0,0 +1,128 @@ +"""Private, read-only Darwin facts after the unchanged Genesis group check. + +This is qualification instrumentation, not an alternative admission detector. +It never signals a process or converts an uncertain result to success. +""" +from __future__ import annotations + +import ctypes +import errno +import hashlib +import json +import os +from pathlib import Path +import subprocess +import sys +import time + + +class BsdInfo(ctypes.Structure): + # Darwin sys/proc_info.h, PROC_PIDTBSDINFO; no argv/environment is read. + _fields_ = [(name, ctypes.c_uint32) for name in ( + "flags", "status", "xstatus", "pid", "ppid", "uid", "gid", "ruid", + "rgid", "svuid", "svgid", "reserved", + )] + [("comm", ctypes.c_char * 16), ("name", ctypes.c_char * 32)] + [ + (name, ctypes.c_uint32) for name in ("nfiles", "pgid", "jobc", "tdev", "tpgid") + ] + [("nice", ctypes.c_int32), ("start_sec", ctypes.c_uint64), ("start_usec", ctypes.c_uint64)] + + +def group_facts(group: int) -> dict: + """Two birth-guarded libproc reads and an independent bounded ps snapshot.""" + if sys.platform != "darwin" or group <= 0: + raise ValueError("Darwin positive process group required") + lib = ctypes.CDLL("/usr/lib/libproc.dylib", use_errno=True) + lib.proc_pidinfo.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_int) + lib.proc_pidinfo.restype = ctypes.c_int + lib.proc_listpids.argtypes = (ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_int) + lib.proc_listpids.restype = ctypes.c_int + lib.proc_pidpath.argtypes = (ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32) + lib.proc_pidpath.restype = ctypes.c_int + buffer = (ctypes.c_int * 4096)() + ctypes.set_errno(0) + size = lib.proc_listpids(2, group, buffer, ctypes.sizeof(buffer)) # PROC_PGRP_ONLY + list_errno = ctypes.get_errno() + result = {"list_bytes": size, "list_errno": list_errno, + "capacity_bytes": ctypes.sizeof(buffer), "members": []} + # Zero is also libproc's error return. Never label it a complete empty + # inventory; ESRCH from signal zero remains a separate observation. + result["inventory_complete"] = (list_errno == 0 and 0 < size < ctypes.sizeof(buffer) + and size % ctypes.sizeof(ctypes.c_int) == 0) + for pid in buffer[:max(0, min(size, ctypes.sizeof(buffer))) // ctypes.sizeof(ctypes.c_int)]: + if pid <= 0: + continue + samples = [] + for _ in range(2): + info = BsdInfo() + ctypes.set_errno(0) + count = lib.proc_pidinfo(pid, 3, 0, ctypes.byref(info), ctypes.sizeof(info)) + if count != ctypes.sizeof(info): + samples.append({"bytes": count, "errno": ctypes.get_errno()}) + continue + try: + session = os.getsid(pid) + except OSError as error: + session = {"errno": error.errno} + executable_buffer = ctypes.create_string_buffer(4096) + path_bytes = lib.proc_pidpath(pid, executable_buffer, len(executable_buffer)) + executable = executable_buffer.value if path_bytes > 0 else b"" + leaf = Path(os.fsdecode(executable)).name if executable else "" + kind = leaf if leaf in {"git", "xcrun", "sandbox-exec", "Python", "python3.14", "codex", "sh"} else "other" + samples.append({"pid": info.pid, "ppid": info.ppid, "pgid": info.pgid, + "status": info.status, "start": [info.start_sec, info.start_usec], + "session": session, "open_files": info.nfiles, + "executable_kind": kind, + "executable_sha256": hashlib.sha256(executable).hexdigest() if executable else None}) + stable = all("start" in sample for sample in samples) and all( + samples[0][key] == samples[1][key] for key in ("pid", "pgid", "start", "executable_sha256")) + result["members"].append({"pid": pid, "samples": samples, "identity_stable": stable}) + try: + completed = subprocess.run(("/bin/ps", "-axo", "pid=,ppid=,pgid=,stat="), + capture_output=True, text=True, timeout=2, check=False) + except (OSError, subprocess.SubprocessError) as error: + result["ps_error"] = type(error).__name__ + result["ps_members"] = None + else: + result["ps_returncode"] = completed.returncode + result["ps_members"] = [line.split() for line in completed.stdout.splitlines() + if len(line.split()) == 4 and line.split()[2] == str(group)] + try: + os.killpg(group, 0) # Observation only; never signals or grants authority. + result["signal_zero"] = "PRESENT" + except OSError as error: + result["signal_zero"] = "ABSENT_ESRCH" if error.errno == errno.ESRCH else "UNCERTAIN" + result["signal_zero_errno"] = error.errno + return result + + +def append_private(path: Path, record: dict) -> None: + fd = os.open(path, os.O_WRONLY | os.O_APPEND | os.O_CREAT | os.O_NOFOLLOW, 0o600) + with os.fdopen(fd, "a", encoding="utf-8") as stream: + stream.write(json.dumps(record, sort_keys=True) + "\n") + stream.flush() + os.fsync(stream.fileno()) + + +def install_observer(service, path: Path) -> None: + original = service._require_group_stopped + + def observed(group, *args, **kwargs): + before = time.monotonic_ns() + try: + value = original(group, *args, **kwargs) + except BaseException as primary: + record = {"group": group, "at_monotonic_ns": before, "detector": "FAIL", + "error_type": type(primary).__name__, "error_code": getattr(primary, "code", None)} + try: + record["facts"] = group_facts(group) + except Exception as error: + record["observation_error"] = type(error).__name__ + try: + append_private(path, record) + except Exception as error: + primary.add_note("Owning read-only observation could not persist: " + type(error).__name__) + raise + else: + append_private(path, {"group": group, "at_monotonic_ns": before, "detector": "PASS_ESRCH"}) + return value + + service._require_group_stopped = observed