mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Require a prepared runtime when assembling voice helper packages (#43121)
## Why The helper's native bindings require shared libraries before entering `main`, including for lifecycle calls. Helper-only packages cannot satisfy that requirement. ## What changed Make `--runtime` mandatory in `third_party/voice/assemble_package.py` and require the `runtime` argument in `assemble()`. Always validate and include the prepared runtime, and update the voice host documentation to describe the requirement. ## Testing Update assembly tests to supply prepared runtimes and verify that package manifests include runtime library hashes and `runtime.json` alongside the helper. GitOrigin-RevId: ab5abf80ae935b4ace833fa59b74f670ba62c632
This commit is contained in:
committed by
copyberry
parent
f6976ab036
commit
e01f38c388
@@ -70,18 +70,20 @@ to reap a dropped helper; explicit `close` waits for process exit.
|
||||
|
||||
For private feasibility artifacts, `third_party/voice/assemble_package.py` copies
|
||||
an existing validated package into a fresh output and adds the helper. Supply
|
||||
`--package`, `--helper`, `--voice-target`, `--build-commit`, and `--output`.
|
||||
`--package`, `--helper`, `--voice-target`, `--build-commit`, `--output`, and
|
||||
`--runtime <prepared-runtime>`.
|
||||
Linux MUSL apps require same-architecture GNU helpers; other targets must match.
|
||||
The package version must end in `+<build-commit>`. The manifest records declared
|
||||
build provenance and file hashes, not authentication or binary architecture proof.
|
||||
Add `--runtime <prepared-runtime>` to include the platform preparer's selected
|
||||
libraries and `runtime.json` beside the helper. The assembler checks the target,
|
||||
The required runtime includes the platform preparer's selected libraries and
|
||||
`runtime.json` beside the helper. The assembler checks the target,
|
||||
pinned source manifest, plugin list, relative paths and file hashes, then checks
|
||||
the copied hashes again. It preserves `lib/` and `plugins/` on macOS, `lib/` and
|
||||
`lib/gstreamer-1.0/` on Linux, and the shared `bin/` on Windows. Unlisted files are
|
||||
not copied. The package manifest records every included runtime file and the
|
||||
unchanged runtime receipt.
|
||||
Omitting `--runtime` retains helper-only assembly.
|
||||
Helper-only assembly is no longer supported: native bindings require shared
|
||||
libraries before the helper enters `main`, including for lifecycle calls.
|
||||
|
||||
This accepts a development runtime receipt, not an authenticated release. It
|
||||
does not repeat native loader inspection or establish trust in the build inputs.
|
||||
@@ -93,7 +95,7 @@ because GStreamer registers process-global callbacks. The small private C ABI
|
||||
bootstrap does not expose native pointers to the parent or link native libraries
|
||||
into ordinary Codex. The existing `libloading` dependency supplies OS loading.
|
||||
Media/privacy controls, a full media binding layer and actual audio proof remain
|
||||
integration stages; helper-only packages continue to support lifecycle calls.
|
||||
integration stages; a prepared runtime is required for packaged lifecycle calls.
|
||||
|
||||
The ignored `packaged_runtime` integration test uses real libraries prepared for
|
||||
the host platform. From `codex-rs`, run
|
||||
|
||||
+15
-14
@@ -1,4 +1,4 @@
|
||||
"""Add a helper and optional prepared runtime to a fresh private Codex package."""
|
||||
"""Add a helper and its prepared runtime to a fresh private Codex package."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
@@ -21,7 +21,7 @@ def assemble(
|
||||
commit: str,
|
||||
output: Path,
|
||||
*,
|
||||
runtime: Path | None = None,
|
||||
runtime: Path,
|
||||
):
|
||||
package, helper = package.resolve(strict=True), helper.resolve(strict=True)
|
||||
output = output.absolute()
|
||||
@@ -71,17 +71,15 @@ def assemble(
|
||||
raise ValueError("helper and app entrypoint must be regular files")
|
||||
if not suffix and not helper.stat().st_mode & 0o111:
|
||||
raise ValueError("helper is not executable")
|
||||
inputs = {}
|
||||
if runtime is not None:
|
||||
runtime = runtime.resolve(strict=True)
|
||||
if any(parent.samefile(package) for parent in (runtime, *runtime.parents)):
|
||||
raise ValueError("runtime must be outside the input package")
|
||||
if any(
|
||||
parent.exists() and parent.samefile(runtime)
|
||||
for parent in output.resolve().parents
|
||||
):
|
||||
raise ValueError("output must be outside the runtime input")
|
||||
inputs = runtime_files(runtime, voice_target)
|
||||
runtime = runtime.resolve(strict=True)
|
||||
if any(parent.samefile(package) for parent in (runtime, *runtime.parents)):
|
||||
raise ValueError("runtime must be outside the input package")
|
||||
if any(
|
||||
parent.exists() and parent.samefile(runtime)
|
||||
for parent in output.resolve().parents
|
||||
):
|
||||
raise ValueError("output must be outside the runtime input")
|
||||
inputs = runtime_files(runtime, voice_target)
|
||||
output.mkdir() # Exclusive creation: never clean or overwrite a pre-existing output.
|
||||
try:
|
||||
shutil.copytree(package, output, dirs_exist_ok=True)
|
||||
@@ -126,7 +124,10 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--build-commit", required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--runtime", type=Path, help="prepared development runtime to include unchanged"
|
||||
"--runtime",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="prepared development runtime required by the helper",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
assemble(
|
||||
|
||||
+39
-3
@@ -297,12 +297,14 @@ class AssembleTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_copies_app_unchanged_and_records_distinct_linux_targets(self):
|
||||
runtime, receipt = self.make_runtime()
|
||||
assemble(
|
||||
self.package,
|
||||
self.helper,
|
||||
"aarch64-unknown-linux-gnu",
|
||||
self.commit,
|
||||
self.output,
|
||||
runtime=runtime,
|
||||
)
|
||||
self.assertEqual((self.output / "bin/codex").read_bytes(), b"unchanged app")
|
||||
self.assertEqual((self.package / "bin/codex").read_bytes(), b"unchanged app")
|
||||
@@ -326,11 +328,19 @@ class AssembleTests(unittest.TestCase):
|
||||
"codex-resources/voice/bin/codex-voice-host": hashlib.sha256(
|
||||
b"private helper"
|
||||
).hexdigest(),
|
||||
**{
|
||||
f"codex-resources/voice/{r['path']}": r["sha256"]
|
||||
for r in receipt["libraries"]
|
||||
},
|
||||
"codex-resources/voice/runtime.json": digest(
|
||||
runtime / "runtime.json"
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_rejects_incompatible_targets_and_unstamped_or_mixed_builds(self):
|
||||
runtime, _ = self.make_runtime()
|
||||
for target, commit in [
|
||||
("aarch64-unknown-linux-musl", self.commit),
|
||||
("x86_64-unknown-linux-gnu", self.commit),
|
||||
@@ -341,7 +351,14 @@ class AssembleTests(unittest.TestCase):
|
||||
self.subTest(target=target, commit=commit),
|
||||
self.assertRaises(ValueError),
|
||||
):
|
||||
assemble(self.package, self.helper, target, commit, self.output)
|
||||
assemble(
|
||||
self.package,
|
||||
self.helper,
|
||||
target,
|
||||
commit,
|
||||
self.output,
|
||||
runtime=runtime,
|
||||
)
|
||||
self.assertFalse(self.output.exists())
|
||||
|
||||
def test_assembles_matching_gnu_linux_app_and_helper_targets(self):
|
||||
@@ -352,8 +369,16 @@ class AssembleTests(unittest.TestCase):
|
||||
(self.package / "codex-package.json").write_text(
|
||||
json.dumps(self.metadata)
|
||||
)
|
||||
output = self.root / target
|
||||
assemble(self.package, self.helper, target, self.commit, output)
|
||||
runtime, receipt = self.make_runtime(target)
|
||||
output = self.root / (target + " packaged")
|
||||
assemble(
|
||||
self.package,
|
||||
self.helper,
|
||||
target,
|
||||
self.commit,
|
||||
output,
|
||||
runtime=runtime,
|
||||
)
|
||||
manifest = json.loads(
|
||||
(output / "codex-resources/voice/manifest.json").read_text()
|
||||
)
|
||||
@@ -370,6 +395,13 @@ class AssembleTests(unittest.TestCase):
|
||||
"codex-resources/voice/bin/codex-voice-host": hashlib.sha256(
|
||||
b"private helper"
|
||||
).hexdigest(),
|
||||
**{
|
||||
f"codex-resources/voice/{r['path']}": r["sha256"]
|
||||
for r in receipt["libraries"]
|
||||
},
|
||||
"codex-resources/voice/runtime.json": digest(
|
||||
runtime / "runtime.json"
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -382,6 +414,7 @@ class AssembleTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_never_replaces_existing_or_nested_outputs(self):
|
||||
runtime, _ = self.make_runtime()
|
||||
for output in (self.package, self.package / "nested", self.helper):
|
||||
with self.subTest(output=output), self.assertRaises(ValueError):
|
||||
assemble(
|
||||
@@ -390,11 +423,13 @@ class AssembleTests(unittest.TestCase):
|
||||
"aarch64-unknown-linux-gnu",
|
||||
self.commit,
|
||||
output,
|
||||
runtime=runtime,
|
||||
)
|
||||
self.assertEqual(self.helper.read_bytes(), b"private helper")
|
||||
self.assertFalse((self.package / "nested").exists())
|
||||
|
||||
def test_cleans_only_its_new_copy_on_failure(self):
|
||||
runtime, _ = self.make_runtime()
|
||||
with patch("assemble_package.shutil.copy2", side_effect=OSError("copy failed")):
|
||||
with self.assertRaises(OSError):
|
||||
assemble(
|
||||
@@ -403,6 +438,7 @@ class AssembleTests(unittest.TestCase):
|
||||
"aarch64-unknown-linux-gnu",
|
||||
self.commit,
|
||||
self.output,
|
||||
runtime=runtime,
|
||||
)
|
||||
self.assertFalse(self.output.exists())
|
||||
self.assertEqual((self.package / "bin/codex").read_bytes(), b"unchanged app")
|
||||
|
||||
Reference in New Issue
Block a user