Skip to content

Invite by GitHub login, list invitations and accept by id - #60

Open
rawwerks wants to merge 1 commit into
mainfrom
feat/org-invitations-by-login
Open

rawwerks wants to merge 1 commit into
mainfrom
feat/org-invitations-by-login

Conversation

@rawwerks

Copy link
Copy Markdown
Contributor

This pull request description was written by an AI agent (Claude Code) working on Raymond Weitekamp's behalf. Raymond has not yet reviewed it.

Organization invitations by GitHub login, an invitation inbox and accept by id

This change maps the hosted service's new organization invitation routes. After it, the CLI covers every route the service's organization interaction exports. The new routes are:

  • GET /organizations/invitations
  • GET /organizations/{name}/invitations
  • POST /organizations/invitations/{id}/accept

Commands

  • prose cli org invite ORG INVITEE invites a GitHub login or an account id.
    • A value that starts with @ is a login, and the @ is dropped.
    • A value that starts with cus_, or that cannot be a GitHub login, is sent as {accountId}. Existing account-id invites therefore keep working.
    • Any other value is sent as {githubLogin}.
    • --login LOGIN and --account ACCOUNT_ID force a reading. Exactly one of the three must be given.
    • The plan's summary names the reading (githubLogin or accountId). The human preview prints it on the Summary: line, which replaces the old Account: line.
    • The receipt carries github_login. The human receipt names @login and both ways to accept.
  • prose cli org invitation list [ORG] lists pending invitations. Without ORG it shows your own. With ORG it shows that organization's, for admins only. The organization id and the issuer's account id are dropped. Human output prints one line per invitation, and your own list ends with the accept command for the first one. --output jsonl prints one record per invitation.
  • prose cli org invitation accept INVITATION_ID accepts by id with a bodyless POST.
    • --token-file still accepts with the token.
    • The two cannot be combined. The error suggests the command without --token-file.
    • Giving neither is an error that suggests org invitation list.
  • org member list and org member role carry github_login. The human list prints member N (@login).

Errors

  • 404 account_not_found becomes SERVICE_RESOURCE_NOT_FOUND with resource {kind: account, id: LOGIN}. The Action reads: "Ask @LOGIN to sign in and run prose cli org list once, then invite them again." This Action is deliberately the plain public command in every output mode, because the invitee runs it on their own machine.
  • 409 ambiguous_login becomes SERVICE_WRITE_CONFLICT. The reason explains the ambiguity, and the Action says to invite with --account instead.
  • Allowlisted codes: account_not_found, ambiguous_login and invalid_login were added to errorClassification.bodyCodes and to the allowlisted serviceCode values.
  • Accept by id: a rejected accept (400) names the possible causes and org invitation list.

Keeping the token secret

Before this change, any positional argument to org invitation accept was presumed to be the token: it was refused and never echoed. accept now takes a positional INVITATION_ID, so the manifest gains an argument format (uuid).

On an operation with a secretFileOptions option, a positional that does not fit its argument's format never fills that argument. The existing secret rule then refuses it without echoing it. This also covers commands rebuilt by typo correction. For example, accept SOMETOKEN still suggests --token-file -, and accept UUID SOMETOKEN suggests removing the extra argument.

The contract test that required secret-reading operations to take no positionals now requires each of their positionals to declare a format.

Contract files

  • operations.v1.json:
    • the new org.invitation.list operation;
    • the reshaped org.invite and org.invitation.accept;
    • the githubLogin and accountId summary fields;
    • the new body codes.
  • operations.schema.json: the argument format property. Summary body names may now be camelCase, as the service's invitation bodies are.
  • runner-error.schema.json: the new service codes and the two new Actions.
  • service-operation.schema.json: the new summary fields.
  • service/organizations.schema.json: github_login, plus invitationList, invitationForMe and pendingInvitation.
  • New response acceptance schemas for both list routes, plus an index entry for accept by id.
  • The service interaction catalog was re-vendored from the service's current public export. The only change is the three routes above.
  • help.v1.json, the agent guide and its generated cases, runner-help.txt, SPEC, docs/service/organizations.md and the CHANGELOG are updated.

The root AGENTS.md convention review does not apply. This change touches no packaging, installation, configuration, credential or state-location plumbing.

Tests

  • Shared corpus: 37 new cases in conformance/cases/service/organizations/, covering:
    • invite by login, @ and the forced readings;
    • conflicting and missing invitees, and invalid logins;
    • account_not_found and ambiguous_login, in JSON and human output;
    • accept by id: yes, preview, confirmation, rejected, both or neither given, a token after the id, and an uppercase id that is not echoed;
    • the inbox and organization lists: JSON, human, empty, JSONL, forbidden, not found and a malformed entry;
    • member logins.
  • Updated cases: the existing invite and accept cases follow the new descriptions, summaries and receipt text.
  • Both ports implement the same contract independently.
  • Unit tests:
    • Bun: the login shape, the format check and the projection of github_login.
    • Rust: the login shape, the suggestion that drops --token-file, the list projections and member github_login.

End to end. The behaviour was also exercised end to end against a local fake of the service's organization routes, with fake accounts. No credentials or hosted deployment were used. The Bun developer build drove one flow:

  • create an organization;
  • invite by login, including a login the service has not seen yet;
  • list the inbox and the admin view;
  • accept by id: wrong account, right account, then a replay;
  • accept with the token on standard input;
  • list members with their logins;
  • change a role;
  • invite by account id, then revoke;
  • invite yourself;
  • make a non-admin invite;
  • make a login ambiguous and invite it;
  • let an invitation expire;
  • demote an issuing admin;
  • call as an account that is not admitted.

Each exchange was then replayed into the Bun and Rust test-seam builds, and both produced identical output.

Checks

These are the author's local runs; CI is the authority. They ran on macOS arm64 with Rust 1.87.0, Python 3.10.21 and Bun 1.4.4. Bun 1.4.4 is newer than the pinned 1.3.5, which was not available on this machine.

Pass:

Gate Result
shared-contracts 82 tests
service-operations-corpus 64 operations, 1207 cases
service-coverage --strict 40/40 interactions, 64 routes
service-help 80 topics
service-operations-bun (--all) 1207 cases, 0 failures
service-operations-rust (--all) 1207 cases, 0 failures
registry-service-bun and registry-service-rust 21 process cases each
differential-conformance 100 cases × 2 products

Also passing: bun-typecheck, bun-build, rust-format, rust-clippy, rust-clippy-dev-endpoint, public-surface, public-surface-files, workflow-policy, local-runner-tests, architecture-tests, architecture-boundary, release-preflight-tests, release-notes, image-bundle, fake-harness, fake-hosted-service, adapter-oracle, windows-resolution-oracle, real-harness-contract and functional-alpha-live-contract.

Fail, environment only, unrelated to this change:

  • dependency-contract: Bun 1.4.4 is installed instead of the pinned 1.3.5.
  • publication-contract: it requires Python 3.10.20, and 3.10.21 is installed.
  • bun-tests: the build-identity and dev-endpoint byte checks fail on Bun 1.4.4, and they fail the same way on unmodified main. The other failures were process and adapter timing tests, a different set on each run, under a load average of about 24. These tests pass in isolation, apart from one more timing case. No adapter or process code is changed here. The organization unit tests pass, 8 of 8.
  • adapter-product-adversary: 3 failures and 1 error in omp and prime adapter parity, under the same load. No adapter code is touched. It was not rerun on main for comparison.

Flaky under load, passed on rerun: rust-tests failed once with a single codex STARTUP_TIMEOUT, then passed on an unchanged rerun.

All of these need a rerun on a CI-equivalent machine before merge.

🤖 Generated with Claude Code

org invite ORG INVITEE sends {githubLogin} for a GitHub login (a leading
@ dropped) and {accountId} for a value that starts with cus_ or cannot be
a login, so account-id invites keep working; --login and --account force
a reading. The plan's summary names the reading (githubLogin, accountId)
and the receipt carries github_login. 404 account_not_found asks the
invitee to run `prose cli org list` once; 409 ambiguous_login says to
invite by account id. Both codes and invalid_login are allowlisted.

org invitation list lists the caller's pending invitations, or with ORG
that organization's (admins). org invitation accept INVITATION_ID
accepts by id with a bodyless POST; --token-file still accepts with the
token, and the two are mutually exclusive. A manifest argument `format`
(uuid) keeps the secret rule: on a secret-reading operation a positional
that does not fit never fills the argument and is never echoed.

Members carry github_login (human `member N (@login)`). The service
interaction catalog is re-vendored with the three new organization
routes. Shared cases, both ports, help, guide, SPEC, docs and CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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