feat(lmux): promote managed Linux workspace service - #602
Merged
Merged
Conversation
Preserve the in-flight lmux M0 design and implementation record that so far existed only in the working tree. No product code changes. Contents: - lmux-contract-m0.md: slices 19-96 (4689 lines), including the shared data-root admission delta that is still candidate, not accepted - lmux-managed-output-capture.md: managed command output capture contract - lmux-capability-projection-plan.md, lmux-live-probe-plan.md, lmux-performance-acceptance-plan.md: remaining M3/M4 acceptance plans - ARD-004: AA stability gate vs interactive metric scale - user-guide lmux pages (en, zh-CN) and their README links Status remains partial: managed CLI preview, Linux launch/lifetime, owned transcripts, discovery, exact-instance connections, close and shared Markdown/action/input binding are implemented; full M3/M4 acceptance and the final goal-wide review are still pending. This commit records evidence only; it does not claim the lmux goal is complete.
Path.mkdir(mode=0o700, parents=True) and os.makedirs(mode=0o700) apply mode to the leaf only: each intermediate level is created through an internal self.parent.mkdir(parents=True, exist_ok=True) call that drops it, so ancestors inherit the process default (0o777 & ~umask). Under a permissive umask such as 0o002 -- the Debian/Ubuntu system-user default in use here -- those ancestors become group-writable 0o775. Private-directory admission rejects a group-writable ancestor as unsafe, so a caller can build a tree and then be refused by its own admission layer. Any commit that introduces such an admission layer must not inherit 0o775 ancestors from this creation path. Add create_private_directory_chain() as the single implementation of the creation rule, shared by the journal owner and the machine-resource control plane instead of two copies of security-relevant directory logic. The journal lock path uses it directly; prepare_private_directory_chain() remains a thin Product-facing wrapper over it that only fixes the entry contract. The primitive also closes two ways the naive form failed: - A directory symlink satisfies is_dir(), so the previous is_dir()/is_symlink() pair accepted it and continued into the link target. Levels are now classified with lstat(), and the FileExistsError race branch reclassifies the same way rather than trusting a following is_dir(). Symlinks, dangling symlinks and non-directory entries are refused and never followed. - mkdir copies an inherited setgid bit, so under a setgid parent mkdir(mode=0o700) yields 0o2700, which admission rejects for not being exactly 0o700. A level this call created is now tightened to 0o700. Only levels created by this call are touched; an existing directory is never rewritten, since it may belong to another owner or hold data this call did not create. Scope is deliberately narrow: creating the chain is not an admission or ownership guarantee, and the module documents that. Callers that own a specific leaf still tighten and validate that leaf through their own authority. Covered by tests/harness/test_private_directory.py for the rule itself (creation mode, setgid, directory/dangling/leaf symlinks, regular file, both concurrent-creator branches, relative target, home expansion), wrapper-only tests in tests/harness/machine_resources/test_control_plane.py, and the existing integration coverage that journal_file_lock routes through the primitive. Verified on a worktree containing only this commit: the mode regression fails before and passes after, and the new suite passes. Not verified here, and not claimed: this commit does not by itself fix any startup failure, because the writer-admission layer that rejects these ancestors is not yet committed. Not addressed: a user whose own HOME is 0o775 is still refused, because the Product deliberately does not rewrite directories it did not create. Relaxing that is a security-contract decision and remains open.
The M0 contract named 1286348e as its starting point. That commit was the tip of harness/linux-interactive-startup-v2, a G18 phase-two line whose own document records that its two 20% primary targets were not met and that it is local delivery only. It is not part of the lmux goal and has been split off with git rebase --onto main 1286348e so the v2 line decides its own destination. Record the split, the range-diff evidence that the eleven retained commits are mechanically equivalent (11/11 "="), and the resulting main baseline, so the contract no longer states a stale starting point.
The M0 contract, its output-capture supplement, ARD-004 and the lmux user guides were committed as a snapshot while the implementation they describe was still uncommitted. Several "implemented / wired / passed" records therefore cannot be reproduced from the commit that carries them: apphost.managed.*, transcript/writer_lease.py, journal/_rooted_io.py, coding/cli/lmux*.py, appserver/managed_mux.py and scripts/dev/reevaluate_g18_comparison.py are all absent from this tree, and the guides document a `lmux` entrypoint that does not exist here. Keep the records, but state the scope so a reader cannot mistake authoring working-tree observations for committed evidence: - lmux-contract-m0.md: add an Evidence scope status field naming the uncommitted implementation and requiring per-slice claims to be read as working-tree observations until the matching code slice lands - ARD-004: change "implemented" to "accepted decision; implementation observed only in the authoring working tree" and name the uncommitted reevaluation tool - user guides (en, zh-CN): state that a build from this commit has no `lmux` entrypoint yet and that the commands are the intended interface, not a shipped one The partial-goal framing was already correct and is unchanged; only the evidence scope was over-claimed.
Add a Linux rooted-file adapter that borrows an already-owned directory descriptor, keeps cleanup and publication receipts explicit, and never falls back to pathname authority after admission. Wire JSONL reads, appends, rewrites, repair and unlink through the borrowed adapter while preserving the existing pathname mode for callers without a retained root. Cover replacement-root attacks, native boundary races, cancellation and cleanup debt, directory bindings, durable deletion, and single-publication inode witnesses.
Introduce the shared retained directory lease, transcript writer lease and store-admission witness used to bind one logical writer to a validated Linux root. Keep creation, binding, process inheritance and cleanup outcomes explicit and fail closed on replacement, unsafe modes, duplicate authority or unknown close results. Cover private root creation, cross-process exclusion, store witnesses, identity replacement, cancellation boundaries and retryable cleanup without importing transcript lifecycle consumers.
Add descriptor-relative blob transaction IO and an explicit blob writer lease on top of the shared directory authority. SessionBlobStore can now borrow a retained data root or operate read-only without reopening mutable pathname authority. Cover root replacement, manifest/object publication, rollback and deletion, read-only rejection, writer exclusion, cleanup debt and identity-preserving recovery.
Route file-backed conversation records and JSON indexes through borrowed rooted handles, and add a shared native-settlement helper so cancellation never turns an accepted filesystem effect into an implicit retry. Preserve the existing pathname mode for unowned callers. Cover replacement roots, index publication and recovery, subprocess exclusion, cancellation, cleanup failure and unknown native outcomes.
Compose retained transcript and blob writers with rooted conversation storage, explicit store-family admission, Product runtime binding and one owner for construction, delivery, import, fork, delete and cleanup. Linux maintenance deletion now requires the caller's retained authority instead of constructing an implicit writer. Carry the authority through catalogs, model-input indexes, images, bundles and session factories. The complete transcript suite covers read-only discovery, process exclusion, replacement races, rollback, cancellation, unknown outcomes and cleanup retry.
feat(lmux): deliver managed Linux workspace service
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Verification
make check-harness: 5186 passed, 66 skippedmake check-harnesstui: 1572 passed, 67 deselectedPromotes the reviewed and merged
lane/harnessresult tomain.