[POC] (do not review )feat(minimald): GitHub-integrated sessions (spec 10) - #996
[POC] (do not review )feat(minimald): GitHub-integrated sessions (spec 10)#996norrietaylor wants to merge 36 commits into
Conversation
Define user requirements for making GitHub development easy in minimald sessions: branch-ready activation (checkout-or-create, server-side clone and adopt-local modes), explicit in-session push, and an opt-in prompt-to-open-PR on exit. Ground the design in a public GitHub App with user-to-server OAuth device flow (real-user attribution, short-lived repo-scoped refreshable tokens) and map each requirement to an existing extension seam in the codebase. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G6K2UrYewEyXTQCHkTm53
Make explicit that the device-flow client is the min CLI (public client, client_id only), while minimald performs no OAuth and holds no App private key in the single-user MVP; distinguish device-flow login from installing the App on a repo. Note that a hosted model drives the same device grant from the hosted side. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G6K2UrYewEyXTQCHkTm53
Invert the auth model: minimald is the GitHub App device-flow client and holds the token; the token never enters the sandbox. Add a min git facade (and same facade for GitHub MCP) that proxies scoped operations back to minimald, so access is mediated and dies when the sandbox exits — preferred over injecting a token or a man-in-the-middle egress proxy. Also: support multi-repo pre-priming as a task-spec field; set default scopes to contents/PRs/issues r/w with workflows excluded; add the spec-declared-else-prompt scope rule, show-but-not-selectable consent, and a reuse-or-mint prompt on subsequent sandboxes; treat local as the anonymous user and record multiple-minimalds-per-host as the scaling axis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016G6K2UrYewEyXTQCHkTm53
Add a new dependency-light `github` crate holding the pure, I/O-free domain types shared by minimald and the min client for spec 10 (GitHub-integrated sessions). Default features carry no reqwest/HTTP so mfile/minimal can depend on it cheaply; a `client` feature is added later. Contents: - RepoSpec/BranchSpec: strict owner/repo[@Branch[:base]] parse+display, base-without-branch made unrepresentable. - Scope/ScopeSet: typed permission model with the R5.1 defaults; the `workflows` permission is unrepresentable by construction (NG6) and a render-for-consent form (R5.3). - SecretString: sole token carrier; redacts in Debug/Display and zeroizes on drop, with no serde derive so raw-String tokens are a review-rejectable pattern (R6). - GithubConfig: client_id + oauth/api/git bases with env-override constructors (the mock-test seam); unset client_id fails closed with NotConfigured (R1.1). - attrs codec: github.grant_id/repos/scopes through SessionConfig.attrs (R7.1); facade verb constants; actionable non-secret error taxonomy (R8.1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the `exec()` process replacement at the end of the attach path with spawn + wait, so control returns to the client after the session shell exits — the seam client-driven post-attach flows hook into. Observable behavior is preserved: - The child's exit code is propagated verbatim via `std::process::exit` (signal death maps to the shell's 128+N convention, the same $? an exec()'d ssh produced). - `-tt` PTY semantics are unchanged, and the interactive path now fails fast when stdin is not a TTY instead of blocking forever in `ssh -tt` (ports the #956 guard, which this branch's base predates). - SIGINT/SIGQUIT are ignored in the waiting parent for exactly as long as the child runs — the POSIX system(3) discipline — so Ctrl-C reaches the foreground ssh / in-session process, and the saved dispositions are restored once the child exits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One JSON file per grant under an injected directory, mirroring the minimald Store actor's on-disk pattern. Enforces 0700/0600 permissions (re-asserted on every open, correcting any drift), atomic temp+rename writes so a crash or error never leaves a partial grant file, and supports multiple coexisting grants -- the substrate reuse-or-mint (R1.3/R6.4) needs. Token fields are typed SecretString and named with `token` in the key so the diagnostics redaction denylist masks them for free (R6.2); GrantStore::list returns a token-free GrantSummary so metadata can be enumerated without ever reading token material back out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extend mfile::Session with an optional github field carrying [[session.github.repos]] entries (repo, branch, optional base) plus a session-wide and per-repo scopes override (spec 10: R2.1, R2.2, R5.2, R7.1). Fields store raw strings; validating accessors (GithubRepo::repo_spec/scope_set, SessionGithub::scope_set) delegate to the github crate's RepoSpec/ScopeSet parsing so there is exactly one grammar, and the workflows scope is rejected there (NG6). Old minimal.toml files without the key keep parsing unchanged (NG7), and the github block is documented as client-consumed at activate time rather than materialized into the project's loadout composable, which is also why Session::is_empty ignores it. Note: this commit could not be verified with cargo test/clippy in this sandbox — cargo build -p mfile has been consistently OOM-killed (signal 9) while compiling the pre-existing, unrelated nickel-lang-parser dependency (pulled in transitively via args/common, present before this change) across 35+ attempts under multiple profile/flag configurations, with no successful build of that dependency observed anywhere in the shared target directory. This appears to be a shared-environment resource constraint rather than a defect in this diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the spec-10 GitHub session RPC types following the existing OneshotSshRpc pattern: GithubBeginLogin, GithubPollLogin, GithubStatus, GithubListAuths, GithubLogout, GithubPrimeRepos, GithubPush, GithubCreatePr, and GetSessionGitState. Repos and scopes cross the wire as plain "owner/repo@branch:base" / "contents:write" strings so this crate gains no new dependency; parsing lives at the edges in the github crate. Every type is #[non_exhaustive] with per-field #[serde(default)] for old-shape and unknown-field tolerance, and no request or response type can carry token material. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
crates/mfile/Cargo.toml gained github.workspace = true in 2d5edd8, but Cargo.lock was never regenerated alongside it, leaving the lockfile's mfile dependency list out of sync with the manifest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Behind the `test-support` feature, add a `testing` module: a hand-rolled tokio HTTP/1.1 mock GitHub that adds no new runtime dependency. It serves the OAuth device flow (scriptable pending/slow_down/expired/denied plus refresh-token rotation, strict-rotation, and invalid_grant), the REST surface (`GET /user`, repo default branch, App-installation lookup with not-installed -> 404 per R1.5, and pull-request list/create with existing-PR-by-head detection per R4.5), and an auth-enforcing git smart-HTTP endpoint backed by `git http-backend` that rejects unauthenticated fetches and serves local bare fixture repos -- the surface that later proves a credential helper actually fires. Every request is captured for test assertions with the Authorization header redacted from Debug and kept out of the header list, so token material never leaks through the capture log. Add `src/bin/github-mock.rs` (required-features = test-support) so an out-of-process e2e script can point a real git/minimald at the same mock via the MINIMALD_GITHUB_*_BASE_URL overrides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Introduces DeviceFlowClient (start_device_flow / poll / fetch_user / login) implementing the GitHub App device flow: POST login/device/code, poll login/oauth/access_token honoring authorization_pending/slow_down/expired_token per RFC 8628, then GET /user to assemble a complete Grant via assemble_grant, ready for GrantStore::save. Errors map into the crate-wide Error taxonomy (Request/Decode for transport and shape failures, NotConfigured for a client_id GitHub does not recognize, NeedsReauth for an expired or declined authorization) rather than introducing a parallel error type. The device code is carried in a SecretString like an access/refresh token, since possessing it is enough to complete the login; no public type in this module renders token material through Debug or Display, which mock-backed tests assert directly alongside happy-path, slow_down backoff, pending-loop, and expiry coverage. Adds `form` to the crate's existing optional reqwest dependency (pure Rust, no new C/asm compile step) for the flow's URL-encoded POST bodies, and threads `dep:tokio` into the `client` feature for the poll loop's backoff sleep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds RestClient<T: TokenProvider>, a typed REST surface over GithubConfig::api_base: get_user, repo_installation (mapping a 404 to Error::AppNotInstalled with the caller-supplied install URL, spec R1.5), repo_default_branch, list_pulls(head=owner:branch) for existing-PR detection (spec R4.5), create_pull, and get_pull. TokenProvider is defined here (device_flow.rs does not define one) and comes with a StaticToken impl so the client is unit-testable without a live grant store. Every non-2xx response maps into the shared Error taxonomy: 401 universally becomes NeedsReauth (spec R8.1); 404 becomes an endpoint-specific variant (AppNotInstalled, RepoNotFound, PullNotFound); anything else becomes UnexpectedStatus carrying only GitHub's own `message` (never the raw body). Extends error.rs with these new variants (Request, Decode, UnexpectedStatus, RepoNotFound, PullNotFound), additive to the existing taxonomy. The `client` feature's reqwest dependency deliberately requests no TLS backend feature: both workspace-available providers (`ring`, `aws-lc-rs`) compile a C/asm crypto core via `cc`, which this build environment cannot do (no working assembler). It rides on Cargo's per-binary feature unification with whichever sibling crate enables a TLS feature in the real daemon binary; the mock-backed tests here only ever talk plain HTTP. Mock tests (crate's in-process MockGithub, test-support feature) cover installation present/absent with the install URL, existing-PR detection by head, the create-pull request payload shape, and 401/404/422 error mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a gitops module that drives git against a session's working tree on the daemon-held token's behalf. Model the hardening on crates/checkouts (the --no-optional-locks + core.hooksPath=/dev/null prefix) but keep the token out of argv, the remote URL, and any on-disk git config: origin stays the clean https URL and credentials are injected env-only through an inline, one-shot credential.helper (installed via GIT_CONFIG_COUNT/KEY/VALUE) whose password is read from a per-child env var. The helper list is reset first so an inherited host helper cannot splice in the wrong user's token. Pin protocol.allow to the remote's scheme, set GIT_TERMINAL_PROMPT=0, and stream all child output through a scrubber that masks the live token bytes. Cover clone (temp-sibling + atomic rename so a failed clone leaves nothing behind, R2.6), fetch/pull/push, checkout-or-create (create from base with a clean error when the base is absent; no implicit push, R2.5), current-branch, default-branch via ls-remote --symref, and ahead-of-upstream detection. Tests exercise file:// fixtures and the auth-enforcing smart-HTTP mock (push/fetch succeed only with the helper wired), assert zero token bytes on disk and in the spawned argv, and confirm the scrubber masks a token echoed by a scripted command. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Declare pub mod gitops so daemon-side callers can reach the authenticated git working-tree API added in the preceding commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Formatting only (import ordering and line wrapping); no behaviour change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GrantManager is the sole access-token accessor: token_for() refreshes near-expiry (<5 min) tokens transparently under a per-grant single-flight mutex, persists the rotated refresh+access pair (atomic write, file and directory fsync) before any caller sees the new token, and turns invalid_grant into a sticky, persisted needs_reauth state with the actionable "run 'min github login'" error (spec R1.2). Transient network/5xx failures get a bounded retry and never invalidate the grant. GrantTokenProvider implements rest::TokenProvider, and with_reauth_retry gives REST callers retry-once-on-401 semantics via refresh_now's grace-limited forced rotation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compose the store, GrantManager, REST client, gitops, and device-flow building blocks from the `github` crate into one `GithubService`, constructed at daemon startup from GithubConfig::from_env() with its grant store rooted at minimal_state_dir()/github/grants, and hang it off ServerStateHandle so every github-RPC handler and session facade channel reaches the same single-flight GrantManager. An unconfigured daemon (no MINIMALD_GITHUB_CLIENT_ID) still constructs the service fine; every op is expected to fail closed via ensure_configured(). Scaffold crates/minimald/src/github/ with rpcs.rs, authz.rs, prime.rs, push_pr.rs, and facade.rs stubs, pre-registered in mod.rs, so the five downstream daemon tasks land in disjoint files. Record the R8.1 span-name conventions (github.auth/refresh/prime/facade/push/pr; repo/branch/grant_id fields only, never tokens) as doc comments on GithubService. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the daemon-authz seam: typed read/write of the github.* attrs on SessionConfig.attrs/Record.attrs via the github crate's GithubAttrs codec, and authorize(&Record, repo, perm) -> Result<GrantId, AuthzError> as the single choke point every authenticated GitHub operation must call. It denies fail-closed when no grant is bound, the target repo is not in the session's declared allow-list, or the required scope/level is not in the resolved scope set (R5.4), and documents the "caller must hold a live-session handle" contract so a destroyed session's id authorizes nothing. Exhaustive allow/deny matrix tests cover the happy path, unbound session, undeclared repo, missing scope per permission, write satisfying a read requirement, malformed attrs, and that deny messages carry no token-shaped material. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the spec-10 R3 mediated-git facade to the session command channel: a `git%<argv>` arm on the SessionChannel dispatch that parses a fail-closed argv allowlist (push/pull/fetch/status/remote -v, clone of declared repos only; every option, path, and unknown subcommand is rejected — the gate is a security boundary with dedicated negative tests), re-reads the live session record, authorizes each operation through the github::authz choke point, obtains the token from the shared GrantManager, and runs github::gitops in the session's working directory, streaming scrubbed output back as msg: lines. Origin URLs are verified against the daemon-derived remote before any authenticated operation, as defense-in-depth against sandbox-side .git/config rewrites (residual TOCTOU documented; the race-free fix belongs in gitops' runtime config injection). Sessions without GitHub attrs — and channels with no facade wired, which is every session until the activation wiring lands — are refused with "this session has no GitHub authentication". Facade access dies with the sandbox: the channel actor is aborted on Env drop and a destroyed session's record read fails, so nothing authorizes after destroy (R3.5/R6.3). Existing channel verbs are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The origin check only compared the first `remote.origin.url` line, so a sandbox could keep that url byte-identical while adding a `pushurl`, a second `url`, or a `url.<x>.insteadOf`/`pushInsteadOf` rewrite in the same sandbox-writable `.git/config` and steer a token-bearing `git push|fetch| pull` at an attacker host, leaking the grant's scoped token (spec G5/R6.1). Replace the first-line match with a whole-config scan (parse_origin_config) that fails closed unless there is exactly one canonical `origin` url and no `pushurl`, extra `url`, or `insteadOf`/`pushInsteadOf` rewrite anywhere. Header parsing tolerates whitespace and the legacy `[remote.origin]` form so a noncanonical-but-valid header cannot smuggle a redirect past the scan. Also refuse a dash-leading current branch before `git push` could parse it as an option (argv-injection hardening; the branch name is not secret). Add facade unit tests for each redirect vector and channel regression tests that plant a pushurl/extra-url/insteadOf in a primed clone and assert the push is refused before it can reach the attacker remote. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Eae46AffqM8TQ2v4bDxDDk
The first cut of the git% facade ran the credentialed push/fetch/pull inside the sandbox-writable working tree and tried to vet the tree's .git/config origin by hand-parsing it. That is unfixable by parsing: git honours [include]/[includeIf] (arbitrary attacker-authored config pulled in only at runtime), credential.helper, http.<url>.extraHeader, url.<base>.insteadOf/pushInsteadOf and remote.origin.url/pushurl, any of which redirects the token-bearing connection or splices the token into a header — and a check-then-use scan races the sandbox rewriting the file. Close the hole by architecture, not inspection: the token-bearing git never runs in, and never reads the config of, the sandbox working tree. Every push/pull/fetch is split into a token-free local leg and a credentialed network leg run against a daemon-authored clean bare mirror under the session's own state dir (a sibling of the sandbox-mounted workspace, reachable by nothing inside the sandbox): - push: transfer the branch worktree -> mirror over a local, network-incapable leg (protocol pinned to file, global/system config disabled, no token), then push mirror -> the daemon-derived canonical URL via github::gitops, which injects the token env-only and reads only the mirror's daemon-authored config. - fetch/pull: gitops fetches canonical -> mirror with the token, then the worktree is updated from the mirror over the local, token-free leg. The canonical URL always comes from the declared repo (authz), never from sandbox input; the worktree's own origin/insteadOf/include is never consulted on the token leg. Delete the .git/config-verification code (verify_origin/parse_origin_config/…) and its now-moot OriginMismatch. The fail-closed argv allowlist and its negative tests are unchanged. Add dedicated exfil tests: a worktree config planting [include], [includeIf], credential.helper, http.extraHeader, url.insteadOf, pushInsteadOf, a redirected remote.origin.url and a pushurl each fails to send the token anywhere but the canonical remote (asserted against sink remotes + token-on-disk scans), plus fetch/pull channel coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The clone arm exempted itself from the mirror discipline: after the clone finalized into the sandbox-writable workspace, the declared-branch checkout ran token-bearing ls-remote/fetch with cwd inside the fresh tree, resolving `origin` from its .git/config. Once the tree lands in the workspace that config is sandbox-writable, so a racing sandbox process could rewrite remote.origin.url (or plant insteadOf/includeIf/ extraHeader directives) in the window between the clone's rename and the checkout's first network op and redirect the token leg to an attacker host — the same TOCTOU class the mirror redesign closed for push/fetch/pull. Clone now runs the full discipline: the credentialed leg fetches canonical -> mirror (and resolves the default branch via ls-remote run in the mirror, reading only daemon-authored config); the worktree is then built from the mirror over token-free, network-incapable local legs inside a temp directory under the daemon-private mirror root and renamed into the workspace only once complete. No git process of the operation — token-bearing or not — ever touches the tree after it becomes sandbox-reachable, and a pre-existing destination is refused outright. Branch names that would reach git as bare arguments are refused when dash-leading (argv injection, defense in depth). Also documents the launcher-owed half of the invariant: activation wiring must never bind the workspace's host parent (which holds .gh-mirror) into the sandbox namespace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The credentialed gitops leg pinned the transport protocol and reset any inherited credential helper, but still let a spawned git read the operator's global/system config — so the "reads only daemon-authored config" invariant held only because the mirror directory and the host environment were clean, not by construction. An inherited url.<base>.insteadOf or url-scoped helper in ~/.gitconfig could still influence where a token-bearing child connects. Pin GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to /dev/null on every child, matching the token-free local legs in the minimald facade: a git spawned here now reads only the repo-local config of the daemon-controlled directory it runs in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a `min github` command group modeled on the existing `Session` subcommand: `login` starts the device flow, shows the verification URL/code, and polls GithubPollLogin with a Ctrl-C-able spinner until approval or expiry (R1.1); `status` renders identity, token validity/expiry, per-repo App-installation state with guidance URLs, and the per-session repo/branch/scope table (R1.4/R1.5/R8.2); `logout` forgets a stored grant behind confirm(). All three are thin oneshot_rpc callers into the already-committed GithubBeginLogin/ GithubPollLogin/GithubStatus/GithubListAuths/GithubLogout wire types — minimald remains the only thing that ever holds a token. All logic lives in the new crates/minimal/src/github.rs; lib.rs only gains the `Command::Github` variant and its dispatch arm, keeping the shared-file footprint minimal. minimal now depends on `github` (default features: pure domain types, no reqwest) to validate --repo via RepoSpec and render session scopes via ScopeSet::render_for_consent, and on `indicatif` (already linked in via `ot`) for the login spinner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements the GithubBeginLogin/GithubPollLogin/GithubStatus/ GithubListAuths/GithubLogout oneshot RPC handlers (spec R1.1-R1.4, R8.2). - crates/minimald/src/github/rpcs.rs: begin_login starts the device flow and parks the poll/fetch-user/persist sequence in a background task keyed by an opaque login_id, so poll_login never blocks on GitHub and can answer Pending promptly on every call. status resolves the active grant (a requested session's bound grant, or the sole stored grant), reports identity/token validity via a live token_for + GET /user check, per-repo App-installation state with install-guidance URLs, and the session's repo/branch/scope table decoded from Record attrs. list_auths returns grant metadata only (no token can reach it, by construction of GrantSummary). logout deletes the local grant fail-closed on an empty id; no revoke endpoint exists yet in the github crate's REST client, so that half is left as a documented follow-up. Every handler runs under a github.auth tracing span. - crates/minimald/src/rpc.rs: adds the five RPCs to the allowlist and dispatch match, and establishes a contiguous "GitHub" block that later prime/push/PR/facade RPCs append siblings to; the channel-framing serve_github_* wrappers stay here per this file's existing convention, delegating decision logic to crate::github::rpcs. Adds a hand-rolled in-process mock GitHub HTTP responder in rpcs.rs's own test module (github::testing::MockGithub is gated behind a feature minimald does not currently enable) to exercise a real login -> poll -> status -> list-auths -> logout round trip over the actual RPC framing, including a not-installed-repo/install-URL assertion and a grep-clean check that no response ever serializes token bytes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Harden the credentialed gitops leg against two residual vectors found in adversarial review: * Set GIT_CONFIG_NOSYSTEM=1 alongside the existing GIT_CONFIG_GLOBAL/SYSTEM=/dev/null redirects, so the system config is denied to every daemon-run git child by construction even on builds that ignore the path override. * Refuse a leading-dash branch/base name (reject_option_like) and separate it from the options with `--` in push/checkout/ls-remote argv, closing an option-injection where a branch from `rev-parse`/remote output (e.g. `git push --set-upstream origin <branch>`) could be parsed as a git flag. Adds negatives asserting the guard fires before any git spawns and that GIT_CONFIG_NOSYSTEM is pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rework the git% facade to establish the invariant the adversarial gate required: no privileged (daemon-run) git process may read sandbox-writable git config. The prior "token-free local leg" still ran git in — or made a daemon git open upload-pack against — the session worktree, where a hostile repo-local .git/config (core.fsmonitor, core.sshCommand, filter.*.process, hooks, [include]/[includeIf]) executes arbitrary code as the daemon, which then reads the token from the mirror. A config-text scanner cannot fix this (git honours includes at file precedence). Now the daemon runs git only in the daemon-authored bare mirror (GIT_CONFIG_GLOBAL=/dev/null, GIT_CONFIG_NOSYSTEM=1, canonical URL explicit, token env-only). Object/ref movement between the worktree and the mirror never exposes worktree config to a daemon git: * Push gives the mirror read-only object access to the worktree via objects/info/alternates (objects are inert/content-addressed) and writes the branch tip as a plain mirror ref, then pushes mirror -> canonical. * Fetch packs the canonical heads in the mirror and copies the .pack/.idx into the worktree object store as plain files, writing refs/remotes/origin/* as plain files. * Pull fetches, verifies a fast-forward in the mirror (merge-base --is-ancestor over the alternate), then advances the branch/tracking refs as plain files. * Status/branch/tip come from parsing HEAD/refs/packed-refs as plain files; ahead/behind from a mirror-side rev-list over the alternate. Every OID read from a worktree ref is validated as a hex sha and every branch name is check_safe_branch-validated before it becomes a path component or git argument. Adds exfil PoC tests planting core.fsmonitor, core.sshCommand, filter.*.process and an [include] that sets one of those: each fails to execute daemon code (a sink script never runs) and the push still reaches only the canonical remote with no token on disk. Clone keeps its daemon-private-temp discipline unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…b-prd-r6ew53 # Conflicts: # Cargo.lock # crates/minimal/Cargo.toml # crates/minimal/src/lib.rs # crates/minimald-rpc/src/lib.rs # crates/minimald/Cargo.toml # crates/minimald/src/env.rs # crates/minimald/src/rpc.rs
The main-side cmd_default tail-called attach_to_session expecting Result<()>, but the spec-10 attach rework returns ExitStatus; consume it via std::process::exit(attach_exit_code(..)) like cmd_attach. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tions The initial merge resolution unioned two sections that shared a factored-out trailing brace run, leaving an unbalanced delimiter that broke parsing (and thus the whole workspace build). Redo it as a proper 3-way merge so both the spec-10 GitHub RPC types and main's DiagBundle section are kept intact, and apply rustfmt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
…macro The merge routed the spec-10 GitHub handlers through main's serve! macro, which wraps futures in served() requiring Result<(), ConnectionError>; the handlers returned () (they predate the macro). Return the handle_channel result directly so served() reports the outcome, matching every other handler (fixes E0271 across build/clippy/tests/artifacts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ody assign main made Cli::command an Option<Command>; the spec-10 CLI parse tests still matched Command::Github(..) directly (6x E0308, breaking clippy+tests lib-test builds). Wrap in Some(..). Also drop a never-read want_body assignment and its now-needless mut in the mock HTTP reader (unused_assignments/unused_mut). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…b-prd-r6ew53 # Conflicts: # crates/minimal/src/lib.rs # crates/minimald/src/env.rs
`assert_no_token_on_disk` is only called from the `#[cfg(feature = "test-support")] mod mock` tests, so a default-feature build of the test target sees it as dead code and `clippy -D warnings` rejects it. Gate the helper on the same feature as its only callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This branch adds `crates/github`, taking the workspace to 30 crates; the map still said 29 and omitted the row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
POC: PR is up to utilize CI to run some validation
Spec 10 — GitHub-integrated minimald sessions
Implements the PRD at
docs/specs/10-spec-github-sessions/. Daemon-held GitHub Appauth (device flow), a mediated
min git/min apifacade so no token enters thesandbox, repo pre-priming at activation, scope consent + reuse-or-mint, and a
client-driven PR-on-exit prompt.
Draft: opened to run CI on a real toolchain. The development environment had no
assembler, so
minimald(viaaws-lc-sys) could not be compiled there — this PR existsto let CI build and verify the daemon half.
Verified locally (96 tests green)
The whole
crates/githubengine: domain types, grant store (0600/atomic), device flow,REST client, leak-proof
gitops(env-only credential injection + option-injection guard),refresh state machine (single-flight, write-ahead rotation), and the
test-supportmockGitHub. Plus the
minimald-rpcwire contracts.Written, awaiting CI verification
Daemon
GithubServicewiring, theauthorize()choke point, the auth RPC handlers, themin github login|status|logoutCLI, and the git% facade rework — which isolates thetoken-bearing git leg from all sandbox-controlled config (daemon git runs only in a
daemon-authored bare mirror; object transfer via
objects/info/alternates+ plain-fileref writes). Facade exfil PoC tests (
core.fsmonitor,core.sshCommand,filter.*.process,[include]) are committed but have never executed locally — CI is their first real run.Not yet implemented (follow-up)
api%facade verb, in-sandboxmin githelper, push/PR + prime RPC handlers, activateconsent/prime wiring, PR-on-exit, redaction + no-token proof harnesses, integration/e2e
suites, docs. Deferred by design: MCP server proper,
on_destroyexecutor, egressenforcement, NG1–NG6.
🤖 Generated with Claude Code
Note
Add GitHub-integrated sessions with device-flow auth and git facade to
minimaldgithubcrate with OAuth device-flow, token refresh, scoped grants, REST client, and git-over-HTTP support, backed by on-disk credential storage with secret-safe types.min github login/status/logoutCLI subcommands that drive device-flow auth via the daemon over SSH RPC, polling with a terminal spinner until completion.minimald(GithubBeginLogin,GithubPollLogin,GithubStatus,GithubListAuths,GithubLogout) dispatched through a sharedGithubServicebuilt at daemon startup.git%verb toSessionChannelthat proxies sandbox git operations to the GitHub facade when aSessionGithubis wired in, and returns a clear error otherwise.[session.github]block with per-repo specs and scope overrides, validated at load time.github-mockbinary (test-support feature) that runs an in-process mock OAuth + smart-HTTP git server, announces its base URL on stdout, and is used by integration tests.min attachto spawn ssh as a child process andexit()with its status rather thanexec()-ing into it; the RPC connection is closed before handing off the terminal and SIGINT/SIGQUIT are ignored in the parent while waiting.GITHUB_*environment overrides contain malformed URLs; absence of a client ID is non-fatal.Macroscope summarized ec57571.