Skip to content

feat(github): gist scope + github.identityProof.create/delete - #1965

Merged
panghy merged 3 commits into
feat/invite-accept-returning-guestfrom
feat/github-identity-proof
Sep 21, 2026
Merged

panghy merged 3 commits into
feat/invite-accept-returning-guestfrom
feat/github-identity-proof

Conversation

@panghy

@panghy panghy commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Stack order — 16/22 of the intentd multiplayer stack. Base: #1963 (feat/invite-accept-returning-guest) · Next: #1967 (feat/invite-challenge-prove).
Review only this PR's own commits (feat/invite-accept-returning-guest..feat/github-identity-proof); everything below is reviewed in its own PR.
Merge intent: the stack lands bottom-up in chain order through the merge queue, one PR at a time — this PR merges only after #1963 has landed. The whole intentd stack merges before the cloudlands-fe multiplayer stack (protocol rule: daemon side first); the cloudlands-fe stack must not be merged, auto-merge-armed, or queued until the intentd stack is merged.

Summary

Guest half of the gist identity-proof join flow: the local daemon can create and delete a secret proof gist with the user's own stored GitHub token, so a host can verify that the joining guest controls the GitHub account.

Changes

  • intent-sourcecontrol
    • device_flow::DEFAULT_SCOPES gains gist (repo, read:org, workflow, gist). Tokens granted before keep working; only the proof needs a re-authorization.
    • New identity_proof module: create_proof_gist(token, api_base, nonce, host_label) -> { gist_id, login }GET /user first, checks X-OAuth-Scopes for gist (typed ScopeMissing), then POST /gists with public: false and a single intent-join-proof.txt (<nonce>\n<explanatory line naming the host>\n); delete_proof_gist(token, api_base, gist_id)DELETE /gists/{id}, 404 treated as success (idempotent). Typed IdentityProofError { ScopeMissing, Unauthorized, Unreachable, Other }.
  • intent-core: Error::IdentityProof(IdentityProofErrorKind) with the bounded wire codes github-not-connected / github-scope-missing / github-unreachable (numeric -32603, error.data = { code }); two new WorkspaceApi trait methods.
  • intent-services: github_identity_proof_create(nonce, hostLabel) / github_identity_proof_delete(gistId) — administrator-gated (refused on the collaborator surfaces), stored sourceControl.github.token only (env / gh fallbacks never consulted), params validated as trimmed single lines / alphanumeric gist id (-32602), API base via the existing INTENTD_GITHUB_API_BASE_URI seam. A token GitHub rejects maps to github-not-connected (same remedy).
  • intent-transport: github.identityProof.create / github.identityProof.delete registered in the catalog + router next to github.getUser; identity-proof errors carry error.data.code.

Wire surface

Method Params Result
github.identityProof.create nonce (req), hostLabel (req) { gistId, login }
github.identityProof.delete gistId (req) { ok: true } (idempotent)

Refusals: -32603 with error.data.codegithub-not-connected | github-scope-missing | github-unreachable; -32602 for bad params.

Protocol docs (docs/protocol/methods/integrations.md §5.27, 09-error-codes.md) are updated on the monorepo side alongside this PR.

Tests

  • intent-sourcecontrol: mock-server unit tests for create (scopes ok), scope-missing, unauthorized, delete live / 404 / unreachable, proof content format.
  • intent-services: services-layer tests (stored token happy path create+delete, no token → not-connected, missing scope, dead host → unreachable, param validation before token load) + error-mapping unit tests.
  • intent-transport: router param/dispatch tests + error.data.code shape; catalog test updated.
  • intentd WSS e2e (e2e_wss_github_device_flow.rs::github_identity_proof_create_and_delete_over_wss): seeded secrets file + mock API host → create returns { gistId, login }, delete idempotent, -32602 on missing params, github-scope-missing without the scope, github-not-connected after github.revoke.

Gates

  • cargo fmt --check ✅, cargo clippy --workspace --all-targets -- -D warnings ✅, cargo test --workspace --test '*_lint'
  • cargo nextest run -p intent-core -p intent-sourcecontrol -p intent-services -p intent-transport → 6063 passed
  • cargo nextest run -p intentd -E 'binary(e2e_wss_github_device_flow) | binary(wss_integration) | test(catalog)' → 174 passed

Not in scope: host-side challenge/prove, client changes, github.authStatus semantics.

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep Code Review Agent🐛

Review completed with 0 suggestions.

Reviewed commit: ac2bfbf

@augmentcode

augmentcode Bot commented Sep 19, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: Adds the guest-side GitHub gist identity-proof flow for multiplayer joins.

Changes:

  • Requests the gist OAuth scope during GitHub device authorization.
  • Adds source-control helpers to check the token owner and scopes, create a secret proof gist, and delete it idempotently.
  • Introduces bounded identity-proof error kinds and serializes their stable codes in JSON-RPC error data.
  • Adds github.identityProof.create and github.identityProof.delete to the workspace API, router, and method catalog.
  • Uses only the stored device-flow token and administrator authorization for both operations.
  • Validates proof text inputs and gist IDs before invoking GitHub.
  • Covers source-control, service, router, catalog, and real WSS transport paths with tests.

Technical Notes: Proof creation verifies X-OAuth-Scopes before publishing a single-file non-public gist and returns only its ID plus the authenticated login.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread crates/intent-sourcecontrol/src/identity_proof.rs
Comment thread crates/intent-sourcecontrol/src/identity_proof.rs Outdated
@panghy
panghy force-pushed the feat/github-identity-proof branch from ac2bfbf to 054acb0 Compare September 19, 2026 14:35
@panghy
panghy force-pushed the feat/github-identity-proof branch from 054acb0 to d0a52c4 Compare September 19, 2026 18:17
@panghy
panghy force-pushed the feat/github-identity-proof branch from d0a52c4 to 3144f45 Compare September 19, 2026 19:01
panghy added a commit that referenced this pull request Sep 20, 2026
…gist scope

`delete_proof_gist` now reads the gist back first (`GET /gists/{id}`):
a 404 stays the idempotent `ok`; otherwise the response's `X-OAuth-Scopes`
must grant `gist` (`ScopeMissing`, like create, instead of a 403 misreported
as a rejected token or a silent success) and the gist must be an Intent
proof gist — exactly one file, `intent-join-proof.txt` — before any DELETE
is sent. Any other gist of the account is refused with the new
`IdentityProofError::NotProofGist`, mapped to `-32602` ("nothing deleted"),
so the RPC can no longer be turned against arbitrary gists. Unit tests pin
the read-back / scope / shape guards; the services and WSS mocks serve the
read-back and the e2e covers the refused unrelated gist and the delete-side
scope check.

Addresses the Deep Code Review threads on #1965.
@panghy
panghy force-pushed the feat/github-identity-proof branch from 3144f45 to 1873395 Compare September 20, 2026 09:06
@panghy
panghy added this pull request to stack #2028 September 20, 2026 12:50
panghy added a commit that referenced this pull request Sep 20, 2026
…gist scope

`delete_proof_gist` now reads the gist back first (`GET /gists/{id}`):
a 404 stays the idempotent `ok`; otherwise the response's `X-OAuth-Scopes`
must grant `gist` (`ScopeMissing`, like create, instead of a 403 misreported
as a rejected token or a silent success) and the gist must be an Intent
proof gist — exactly one file, `intent-join-proof.txt` — before any DELETE
is sent. Any other gist of the account is refused with the new
`IdentityProofError::NotProofGist`, mapped to `-32602` ("nothing deleted"),
so the RPC can no longer be turned against arbitrary gists. Unit tests pin
the read-back / scope / shape guards; the services and WSS mocks serve the
read-back and the e2e covers the refused unrelated gist and the delete-side
scope check.

Addresses the Deep Code Review threads on #1965.
@panghy
panghy force-pushed the feat/github-identity-proof branch from 1873395 to 08a82bf Compare September 20, 2026 14:10
@panghy
panghy added this pull request to the merge queue Sep 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was removed Sep 20, 2026
@panghy
panghy force-pushed the feat/github-identity-proof branch from 08a82bf to 299c22b Compare September 20, 2026 15:29
@panghy
panghy added this pull request to the merge queue Sep 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was removed Sep 20, 2026
@panghy
panghy added this pull request to the merge queue Sep 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was removed Sep 20, 2026
Guest half of the gist identity-proof join flow: the local daemon creates
and deletes a secret proof gist with the user's own stored GitHub token.

- device_flow: add `gist` to DEFAULT_SCOPES
- sourcecontrol: identity_proof module — GET /user (X-OAuth-Scopes check),
  POST /gists public:false, idempotent DELETE /gists/{id}
- services/transport: github.identityProof.create { nonce, hostLabel } ->
  { gistId, login }; github.identityProof.delete { gistId } -> { ok: true };
  owner-client only; bounded -32603 error.data.code
  (github-not-connected / github-scope-missing / github-unreachable)
- tests: engine mock-server unit tests, services + router tests, WSS e2e

Agent-Id: agent-c6117b95-ceb4-438e-93c1-2cbe4c0d5853
…gist scope

`delete_proof_gist` now reads the gist back first (`GET /gists/{id}`):
a 404 stays the idempotent `ok`; otherwise the response's `X-OAuth-Scopes`
must grant `gist` (`ScopeMissing`, like create, instead of a 403 misreported
as a rejected token or a silent success) and the gist must be an Intent
proof gist — exactly one file, `intent-join-proof.txt` — before any DELETE
is sent. Any other gist of the account is refused with the new
`IdentityProofError::NotProofGist`, mapped to `-32602` ("nothing deleted"),
so the RPC can no longer be turned against arbitrary gists. Unit tests pin
the read-back / scope / shape guards; the services and WSS mocks serve the
read-back and the e2e covers the refused unrelated gist and the delete-side
scope check.

Addresses the Deep Code Review threads on #1965.
…tityProof.delete

`delete_proof_gist` read `GET /gists/{id}` first and mapped its status before
looking at `X-OAuth-Scopes`, so a token without the `gist` scope got the wrong
answer whenever GitHub would not show it the gist: a 404 read as "already
deleted" (false `Ok`) and a 403 as a rejected token (`Unauthorized`).

Delete now runs the same authenticated `GET /user` scope preflight as create
before touching the gist, so the matrix is: missing scope + 404/403 →
`ScopeMissing`; scoped + 404 → `Ok` (idempotent); rejected token →
`Unauthorized`. The read-back stays for the proof-gist guard only.

Regression tests: missing scope on a 404 gist is not success, missing scope
on a 403 gist is `ScopeMissing` (from the independent verifier), the scoped
404 idempotence case, and a WSS assertion that an unknown gist without the
scope is `github-scope-missing`, not `{ ok: true }`.
@panghy
panghy force-pushed the feat/github-identity-proof branch from 299c22b to 041edca Compare September 20, 2026 18:21
@panghy
panghy added this pull request to the merge queue Sep 20, 2026
An error occurred while trying to automatically change base from feat/invite-accept-returning-guest to main September 20, 2026 23:54
Merged via the queue into main with commit e8e341d Sep 21, 2026
42 of 56 checks passed
@panghy
panghy deleted the feat/github-identity-proof branch September 21, 2026 00:01
@panghy panghy mentioned this pull request Sep 21, 2026
panghy added a commit that referenced this pull request Sep 21, 2026
Resolves adjacent-insertion conflicts against the gist identity-proof series (#1965, #1967, #1969): main's github.identityProof.* router arms, identity_proof module and invite doc comment are kept byte-identical with our sourceControl.* / gitlab_* insertions after them, and the totals are recounted to 332 router / 56 fast-path / 390 client-callable (main 327/56/385 + our 5 router methods) in catalog/tests.rs and the protocol.rs 10.5 paragraph.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant