Specify repository creation and the OpenAgents CLI

62aed84fc1ec · AtlantisPleb · · parent 92df83e419b3

Specify repository creation and the OpenAgents CLI

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified docs/repository-creation-and-openagents-cli-spec.md
  • added docs/repository-creation-cli-implementation-roadmap.md

Diff

2 files changed, +914 -133

docs/repository-creation-and-openagents-cli-spec.md modified +435 -133

@@ -1,14 +1,15 @@

1
# Repository creation and OpenAgents CLI specification
1
# Repository creation, GitHub import, and OpenAgents CLI specification
2 2
3 3
Date: 2026-08-20
4 4
5
Status: Proposed; repository creation is the first delivery slice
5
Status: Implemented for local verification; staging release remains gated
6 6
7 7
## Purpose
8 8
9 9
Allow an authenticated person or agent to create an OpenAgents-hosted Git
10
repository, push and pull with standard Git, and manage the repository through
11
the browser or a first-party CLI.
10
repository, import an existing GitHub repository once, push and pull with
11
standard Git, and manage the repository through the browser or a first-party
12
CLI.
12 13
13 14
This specification uses Cursor Origin as product research. It adopts the useful
14 15
interaction patterns without treating Origin's API, implementation, pricing,

@@ -31,14 +32,16 @@ second repository database or infer authorization from local Git state.

31 32
- [OpenAgents architecture](architecture.md)
32 33
- [GitHub-shaped Issues and Projects API assessment](github-api-issues-projects-assessment.md)
33 34
- [Issues and Projects UI roadmap](issues-projects-ui-roadmap.md)
35
- [Repository creation and CLI implementation roadmap](repository-creation-cli-implementation-roadmap.md)
34 36
- [API authentication](api-authentication.md)
35 37
- [GitHub authentication and token lifecycle](github-auth-plan.md)
36 38
- [ADR 0007: Cut over to forge-canonical source control after proof](decisions/0007-cut-over-to-forge-canonical-source-control-after-proof.md)
37 39
- [Integration hardening and staging readiness recommendations](2026-08-20-integration-hardening-and-staging-readiness-recommendations.md)
38 40
39
## Current state
41
## Baseline before implementation
40 42
41
The application already has several parts of the required domain:
43
Before this slice, the application already had several parts of the required
44
domain:
42 45
43 46
- `OpenAgents.Repositories.Repository` stores a UUID, owner, name, visibility,
44 47
  and default branch.

@@ -52,10 +55,12 @@ The application already has several parts of the required domain:

52 55
  projections.
53 56
- The first-party `oa_pat_...` token supports the `forge:write` scope for
54 57
  GitHub-shaped API mutations.
58
- The server retains the signed-in user's encrypted GitHub `repo` grant and has
59
  bounded adapters that list repositories and read repository content.
55 60
56
The current implementation does not provide a user repository lifecycle:
61
The baseline did not provide a user repository lifecycle:
57 62
58
- No browser route or public API creates a repository.
63
- No browser route or public API creates or imports a repository.
59 64
- No API lists every repository the current user can access.
60 65
- The signed-in dashboard loads only `OpenAgentsInc/openagents.com`.
61 66
- Git repository admission comes from `OPENAGENTS_FORGE_REPOSITORIES`, not the

@@ -67,16 +72,21 @@ The current implementation does not provide a user repository lifecycle:

67 72
  checking an ordinary user's repository membership.
68 73
- The public code route has a literal `OpenAgentsInc` owner segment.
69 74
70
Creating a database row alone therefore does not create a usable hosted Git
71
repository.
75
Creating a database row alone therefore did not create a usable hosted Git
76
repository. The implementation described below now joins the database,
77
provisioning, WAL, Git HTTP, browser, and CLI paths. The implementation roadmap
78
tracks the remaining local end-to-end and staging evidence.
72 79
73 80
## Product decisions
74 81
75 82
The first release follows these decisions:
76 83
77
- A repository belongs to one stable namespace.
78
- The first namespace type is a personal namespace. Team and organization
79
  namespaces follow later.
84
- A repository belongs to a GitHub-backed user or organization namespace.
85
- OpenAgents uses the same user and organization names that GitHub reports. It
86
  keys each namespace by GitHub's immutable numeric account ID instead of a
87
  mutable login string.
88
- A namespace is available only through GitHub sign-in and the retained GitHub
89
  connection. Custom OpenAgents namespaces follow later.
80 90
- Repository visibility is `private` or `public`. The `internal` visibility
81 91
  from Origin requires a team model and is out of scope.
82 92
- New repositories default to `private` and use `main` as the default branch.

@@ -89,16 +99,21 @@ The first release follows these decisions:

89 99
  commands but does not replace Git.
90 100
- The CLI uses the server-provided clone URL instead of constructing one from a
91 101
  hard-coded host.
92
- GitHub mirroring, pull requests, rulesets, SSH keys, apps, and team sharing
102
- The first release imports Git repository history, branches, and tags from
103
  GitHub as a one-time copy. It does not maintain a mirror or two-way sync.
104
- Pull requests, rulesets, SSH keys, apps, and OpenAgents-native team sharing
93 105
  remain future slices.
94 106
95 107
## Goals
96 108
97
The repository-creation slice must support these outcomes:
109
The repository and import slice must support these outcomes:
98 110
99
- You can claim or receive a stable personal namespace.
111
- Your OpenAgents namespaces match your GitHub user and eligible GitHub
112
  organization namespaces.
100 113
- You can create an empty repository from the browser.
101 114
- You can create the same repository with `openagents repo create`.
115
- You can import a GitHub repository from the browser or with
116
  `openagents repo import`.
102 117
- You can list and view repositories that you can access.
103 118
- You can clone, fetch, push, and pull with standard Git.
104 119
- A public repository supports anonymous read access.

@@ -113,9 +128,10 @@ The repository-creation slice must support these outcomes:

113 128
114 129
The first slice does not include:
115 130
116
- GitHub repository mirroring or bidirectional synchronization.
131
- Continuous or bidirectional GitHub synchronization after a one-time import.
117 132
- Pull requests, reviews, merge queues, rulesets, or branch protection.
118
- Team namespaces, organization administration, or `internal` visibility.
133
- OpenAgents-native teams, custom namespaces, organization administration, or
134
  `internal` visibility.
119 135
- Repository transfer, rename, archive, restore, or deletion.
120 136
- SSH Git transport or SSH-key management.
121 137
- Code search across repositories.

@@ -138,54 +154,81 @@ product promises.

138 154
| Human and JSON output | Adopt as separate output contracts |
139 155
| Endpoint override | Adopt for local development and staging |
140 156
| Repository deletion | Defer until recovery and retention semantics exist |
141
| GitHub mirror creation | Defer until per-repository source authority and synchronization receipts exist |
157
| GitHub mirror creation | Adapt as a receipted one-time import; defer continuous synchronization |
142 158
| Pull requests and rulesets | Defer until their server domains exist |
143 159
| SSH keys | Defer; use HTTPS and the Git credential helper first |
144 160
| Generic authenticated API command | Reserve until endpoint and secret-redaction behavior is specified |
145 161
| Self-update and shell completion | Defer until CLI packaging and release channels are admitted |
146 162
147 163
Origin uses team-owned codebases and `internal` visibility. OpenAgents starts
148
with stable personal namespaces and `public` or `private` repositories because
149
those concepts already match the application domain.
164
with GitHub-backed user and organization namespaces and `public` or `private`
165
repositories because those concepts match the existing identity and repository
166
domains.
150 167
151 168
## Namespace model
152 169
153
A repository owner must be a durable namespace, not a mutable GitHub login
154
copied into a string field.
170
GitHub supplies the namespace system for the first release. A person signs in
171
with GitHub and sees the same user login and eligible organization logins as
172
repository owners in OpenAgents. OpenAgents does not ask the person to claim a
173
second name.
174
175
Key a namespace by GitHub's immutable numeric account ID and account type. Treat
176
the GitHub login as a mutable URL and display projection.
155 177
156 178
Add a `namespaces` table with at least these fields:
157 179
158 180
| Field | Meaning |
159 181
| --- | --- |
160
| `id` | Stable UUID |
161
| `slug` | Displayed, URL-safe owner name |
162
| `slug_key` | Case-insensitive uniqueness key |
163
| `kind` | `personal` initially; reserve `team` |
164
| `owner_user_id` | Owning user for a personal namespace |
182
| `id` | Stable OpenAgents UUID |
183
| `provider` | `github` in the first release |
184
| `provider_account_id` | Immutable GitHub numeric user or organization ID |
185
| `provider_node_id` | GitHub GraphQL node ID when supplied |
186
| `slug` | Current GitHub user or organization login |
187
| `slug_key` | Case-insensitive routing key |
188
| `kind` | `user` or `organization` |
189
| `owner_user_id` | Local user for a GitHub user namespace; `nil` for an organization |
190
| `provider_refreshed_at` | Time of the latest successful GitHub projection refresh |
165 191
| `state` | `active`, `suspended`, or `retired` |
166 192
| `inserted_at`, `updated_at` | Audit timestamps |
167 193
194
Enforce uniqueness on `{provider, provider_account_id, kind}` and on the active
195
case-insensitive slug. A client-supplied slug never establishes namespace
196
ownership.
197
168 198
The repository row gains a required `namespace_id`. Keep `owner` as a derived
169 199
API projection during migration only if compatibility requires it. New policy
170 200
checks must join through `namespace_id` and memberships instead of trusting the
171 201
projected owner string.
172 202
173
### Personal namespace enrollment
203
### GitHub namespace projection
174 204
175 205
Use this first-release flow:
176 206
177
1. Derive a suggested slug from the authenticated user's current GitHub login.
178
2. Normalize and validate the suggestion.
179
3. Claim the slug transactionally on the user's first repository creation.
180
4. Keep the claimed slug stable when the GitHub login later changes.
181
5. If another namespace already owns the slug, return `namespace_required` and
182
   direct the user to an authenticated namespace-selection page.
183
184
Do not silently append an unstable number to resolve a collision. The user must
185
know the namespace that appears in clone URLs.
186
187
Namespace rename and transfer are out of scope. The UI must say that the chosen
188
namespace cannot be changed during the initial release.
207
1. On GitHub sign-in, upsert the GitHub user namespace from the verified numeric
208
   `github_id` and current `github_login` already stored on the local user.
209
2. Read the person's active GitHub organization memberships and upsert each
210
   organization from its numeric ID, node ID, and login.
211
3. Before an organization create or import, refresh that organization and the
212
   caller's active membership from GitHub.
213
4. Require the organization `admin` role for an empty repository creation in
214
   the first release.
215
5. For import, require read access to the source repository and authority to
216
   create the destination in the matching namespace. An organization import
217
   therefore requires the organization `admin` role under the first-release
218
   create policy, but it does not require admin permission on the source
219
   repository itself.
220
6. Add the creator as the OpenAgents repository `owner` in the repository
221
   transaction. Do not grant every GitHub organization member repository access
222
   until an organization access policy is specified.
223
224
GitHub logins can change while numeric account IDs remain stable. When GitHub
225
reports a rename, update the namespace slug and retain the prior slug in a
226
`namespace_aliases` table so existing web and Git URLs continue to resolve. An
227
alias can route to the stable namespace but cannot authorize a mutation.
228
229
Custom namespace claim, rename, transfer, and non-GitHub identity providers are
230
out of scope. They can evolve later without changing existing GitHub numeric
231
identity keys.
189 232
190 233
### Repository name rules
191 234

@@ -229,6 +272,7 @@ The repository row needs these additional attributes:

229 272
| `created_by_user_id` | Audited creator |
230 273
| `description` | Optional bounded description |
231 274
| `lifecycle_state` | Provisioning and availability state |
275
| `provisioning_kind` | `empty` or `github_import` |
232 276
| `provision_error_code` | Bounded operational code without provider prose |
233 277
| `storage_key` | Stable UUID-derived forge storage key |
234 278
| `ready_at` | Time when Git operations became available |

@@ -236,6 +280,42 @@ The repository row needs these additional attributes:

236 280
Do not put a credential, filesystem path, bucket URL, or raw provisioning error
237 281
in the repository row.
238 282
283
### GitHub import record
284
285
A one-time import is a durable provisioning operation, not a mirror. Add a
286
`repository_imports` table with at least these fields:
287
288
| Field | Meaning |
289
| --- | --- |
290
| `id` | Stable import UUID |
291
| `repository_id` | Destination OpenAgents repository |
292
| `provider` | `github` |
293
| `source_repository_id` | Immutable GitHub numeric repository ID |
294
| `source_owner_id` | Immutable GitHub numeric user or organization ID |
295
| `source_full_name` | Bounded source owner and repository projection |
296
| `source_default_branch` | GitHub default branch observed at acceptance |
297
| `source_ref_digest` | Digest of the accepted branch and tag ref map |
298
| `source_head_sha` | Accepted default-branch head when one exists |
299
| `state` | `pending`, `running`, `completed`, or `failed` |
300
| `attempt_count` | Bounded retry count |
301
| `error_code` | Bounded operational code |
302
| `started_at`, `completed_at` | Import timing |
303
304
The record never stores a GitHub access token, authenticated clone URL, local
305
path, raw Git diagnostic, or repository content.
306
307
At import acceptance, resolve the source through the GitHub API and freeze the
308
advertised `refs/heads/*` and `refs/tags/*` map. The import copies the objects
309
reachable from that map and verifies the same ref digest before it marks the
310
destination ready. GitHub changes after the accepted snapshot are not part of
311
the import.
312
313
The first release imports standard Git history, branches, tags, the default
314
branch, and submodule pointer commits. It does not import GitHub Issues, pull
315
requests, reviews, Actions runs or secrets, releases, repository settings,
316
wikis, or Git LFS objects. Git LFS pointer files remain ordinary Git content;
317
the UI and CLI must warn when the source uses LFS.
318
239 319
### Authority split
240 320
241 321
The lifecycle preserves these authorities:

@@ -258,32 +338,42 @@ creation must never add it to the deployable-repository allowlist.

258 338
259 339
## Provisioning contract
260 340
261
Repository creation crosses PostgreSQL and durable Git storage, so one database
262
transaction cannot complete the entire operation. Use a transactional outbox
263
and an idempotent provisioner.
341
Repository creation and GitHub import cross PostgreSQL, GitHub, and durable Git
342
storage, so one database transaction cannot complete the entire operation. Use
343
a transactional outbox and an idempotent provisioner.
264 344
265 345
1. Validate the authenticated principal, namespace, name, visibility, default
266 346
   branch, quota, and idempotency key.
267
2. In one PostgreSQL transaction, create the repository, add the creator as an
268
   `owner` member, and insert a provisioning outbox record.
269
3. Commit before any filesystem or object-store operation begins.
270
4. Initialize an empty durable WAL namespace using `repository.storage_key`.
271
5. Materialize or initialize the bare-repository cache with the requested
347
2. For an import, resolve the GitHub source repository, immutable owner and
348
   repository IDs, caller permission, default branch, and accepted ref map with
349
   the caller's retained server-side GitHub grant.
350
3. In one PostgreSQL transaction, create the repository, add the creator as an
351
   `owner` member, insert the optional import record, and insert a provisioning
352
   outbox record.
353
4. Commit before any filesystem, Git, or object-store operation begins.
354
5. Initialize an empty durable WAL namespace using `repository.storage_key`.
355
6. For an import, fetch the accepted GitHub refs into an isolated temporary
356
   repository, verify the frozen ref digest, and ingest the accepted objects and
357
   refs into the destination WAL. Use a server-owned credential adapter that
358
   never places the GitHub token in a URL, argv, log, receipt, or repository
359
   configuration.
360
7. Materialize or initialize the bare-repository cache with the selected
272 361
   symbolic default branch.
273
6. Verify that upload-pack and receive-pack resolve the same repository UUID.
274
7. Mark the repository `ready` and set `ready_at`.
275
8. On failure, record a bounded `provision_error_code`, mark the repository
362
8. Verify that upload-pack and receive-pack resolve the same repository UUID.
363
9. Mark the optional import `completed`, mark the repository `ready`, and set
364
   `ready_at`.
365
10. On failure, record a bounded `provision_error_code`, mark the repository
276 366
   `failed`, and retain the outbox attempt history.
277 367
278 368
The provisioner must tolerate a crash after every step. A retry must converge
279 369
on the same repository and storage namespace without deleting accepted Git
280 370
objects.
281 371
282
The create API can wait for a bounded synchronous attempt. Return `201 Created`
283
when provisioning completes during that window. Return `202 Accepted` with
284
`lifecycle_state: "provisioning"` when work continues asynchronously. The CLI
285
polls the repository resource until it reaches `ready`, reaches `failed`, or
286
exceeds its client timeout.
372
The create and import APIs can wait for a bounded synchronous attempt. Return
373
`201 Created` when provisioning completes during that window. Return `202
374
Accepted` with `lifecycle_state: "provisioning"` when work continues
375
asynchronously. The CLI polls the repository resource until it reaches `ready`,
376
reaches `failed`, or exceeds its client timeout.
287 377
288 378
## API contract
289 379

@@ -293,18 +383,24 @@ The new surface extends the bounded GitHub-shaped API under `/api/v3`.

293 383
294 384
| Method and path | Authority | First release behavior |
295 385
| --- | --- | --- |
296
| `POST /api/v3/user/repos` | Authenticated API with `forge:write` | Create in the caller's personal namespace |
386
| `POST /api/v3/user/repos` | Authenticated API with `forge:write` | Create in the caller's GitHub user namespace |
387
| `POST /api/v3/orgs/{org}/repos` | Authenticated API with `forge:write` | Create in an eligible GitHub organization namespace |
388
| `POST /api/v3/user/repos/imports` | Authenticated API with `forge:write` | Import a GitHub repository into the caller's user namespace |
389
| `POST /api/v3/orgs/{org}/repos/imports` | Authenticated API with `forge:write` | Import a GitHub organization repository into its matching organization namespace |
297 390
| `GET /api/v3/user/repos` | Authenticated API | List repositories visible to the caller, including private repositories |
298 391
| `GET /api/v3/repos/{owner}/{repo}` | Optional API principal | Return a public repository or a repository visible to the principal |
392
| `GET /api/v3/repository-imports/{id}` | Authenticated API | Return bounded status for an import owned by the caller |
299 393
| Git smart HTTP under `/git/{owner}/{repo}.git` | Public read or authenticated Git principal | Clone, fetch, push, and pull |
300 394
301
Reserve `POST /api/v3/orgs/{org}/repos` for a future team namespace. Do not add
302
an endpoint that accepts an arbitrary `owner` string and then creates a personal
303
repository there.
395
Do not add an endpoint that accepts an arbitrary owner string. The user route
396
derives the GitHub user namespace from the authenticated principal. The
397
organization routes resolve `{org}` to a refreshed GitHub organization ID and
398
verify the caller's active GitHub authority before creating an OpenAgents row.
304 399
305 400
The current route classifier treats all `/api/v3` `GET` requests as public.
306
`GET /api/v3/user/repos` is an authenticated exception and needs an explicit
307
route-authority declaration and test.
401
`GET /api/v3/user/repos` and `GET /api/v3/repository-imports/{id}` are
402
authenticated exceptions and need explicit route-authority declarations and
403
tests.
308 404
309 405
### Create request
310 406

@@ -330,13 +426,63 @@ Rules:

330 426
- `description` is optional and bounded to 350 Unicode scalar values.
331 427
- `private` defaults to `true`.
332 428
- `default_branch` defaults to `main` and follows Git ref-name validation.
333
- The server derives the personal namespace from the authenticated principal.
429
- The server derives the GitHub user namespace from the authenticated
430
  principal. Organization creation uses the organization route.
334 431
- `Idempotency-Key` is required for the CLI and recommended for every client.
335 432
336 433
The same principal, idempotency key, and normalized request returns the original
337 434
result. Reusing the key with a different normalized request returns `409
338 435
Conflict`.
339 436
437
### Import request
438
439
Import into the matching GitHub user namespace:
440
441
```http
442
POST /api/v3/user/repos/imports
443
Authorization: Bearer oa_pat_...
444
Content-Type: application/json
445
Idempotency-Key: f5a7dc80-a670-42ce-9454-fc5e5e64586f
446
```
447
448
```json
449
{
450
  "source": {
451
    "provider": "github",
452
    "repository": "octavia/existing-project"
453
  },
454
  "name": "existing-project",
455
  "private": true
456
}
457
```
458
459
Import an organization-owned source into its matching organization namespace
460
through `/api/v3/orgs/{org}/repos/imports`. The `{org}` path ID must resolve to
461
the same immutable GitHub owner ID returned for the source repository.
462
463
Rules:
464
465
- The source is a GitHub `owner/name`, not a caller-supplied clone URL.
466
- The server resolves the source and its permissions with the signed-in user's
467
  retained, encrypted GitHub token.
468
- The user route accepts a source owned by the signed-in GitHub user. Copying a
469
  repository owned by another account into the GitHub user namespace is a
470
  future fork or template workflow.
471
- The organization route requires read access to the source repository, active
472
  organization membership, and the organization `admin` role required by the
473
  first-release destination create policy.
474
- `name` defaults to the normalized GitHub repository name.
475
- `private` defaults to `true`, including when the GitHub source is public. A
476
  private source can never default to public.
477
- `default_branch` defaults to the source default branch.
478
- The server creates an independent OpenAgents repository. It installs no
479
  webhook and schedules no later GitHub fetch or push.
480
481
Return the repository projection with an `import` object containing only the
482
import ID, provider, source full name, accepted head SHA, state, and timestamps.
483
Return `202 Accepted` while the import runs and `201 Created` only when the
484
repository is already `ready`.
485
340 486
### Repository response
341 487
342 488
```json

@@ -380,7 +526,7 @@ required field has the wrong type.

380 526
| `404 Not Found` | Repository is absent or hidden from the principal |
381 527
| `409 Conflict` | Name or idempotency conflict |
382 528
| `422 Unprocessable Entity` | Valid JSON violates repository rules |
383
| `503 Service Unavailable` | Provisioning cannot currently start |
529
| `503 Service Unavailable` | Provisioning or required GitHub access cannot currently start |
384 530
385 531
Use one bounded error envelope:
386 532

@@ -420,6 +566,38 @@ token authentication. A scope never grants access to every repository.

420 566
Existing issue and project clients retain their current behavior. Add scope or
421 567
token-format changes only through a separately documented migration.
422 568
569
### Retained GitHub grant
570
571
GitHub namespace projection and import use the retained, encrypted GitHub OAuth
572
token on the server. The OpenAgents PAT proves the CLI caller's OpenAgents
573
authority; it never becomes a GitHub credential and never receives the GitHub
574
token.
575
576
The existing `repo` grant supplies repository access for public and private
577
imports. Reading private organization membership requires an explicit
578
organization-read decision. The first release proposes adding `read:org` so the
579
server can enumerate active organization memberships and roles through
580
GitHub's organization-membership API. GitHub documents `repo` and `read:org`
581
separately in its
582
[OAuth scope reference](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps).
583
584
Before activation:
585
586
1. Update the GitHub consent UI to explain repository import and organization
587
   namespace projection.
588
2. Update the exact granted-scope validation.
589
3. Require existing users to reconnect rather than rewriting stored scope
590
   metadata.
591
4. Use `Req` for GitHub REST calls and explicit fakes in tests.
592
5. Fail an import with `github_connection_required` when the user has no
593
   retained grant.
594
6. Fail organization operations closed when GitHub cannot refresh the required
595
   membership or repository permission.
596
597
Import never installs a GitHub webhook, writes to GitHub, or schedules a later
598
sync. After the import completes, OpenAgents is the source of truth for the new
599
repository and GitHub remains unchanged.
600
423 601
### Browser-assisted CLI login
424 602
425 603
Add a device-style browser flow so a terminal never handles the GitHub OAuth

@@ -504,6 +682,7 @@ The list includes:

504 682
- Updated time.
505 683
- Open issue count when available without an unbounded query.
506 684
- A **New repository** action.
685
- An **Import from GitHub** action.
507 686
508 687
Use a LiveView stream and separate count and empty-state assigns. Add search and
509 688
pagination after the base list works.

@@ -512,7 +691,8 @@ pagination after the base list works.

512 691
513 692
Add an authenticated `/repositories/new` route with:
514 693
515
- A read-only or selectable namespace field, depending on enrollment state.
694
- A namespace selector populated from the signed-in GitHub user and eligible
695
  GitHub organizations.
516 696
- A repository name input.
517 697
- An optional description.
518 698
- A `private` or `public` visibility choice, with `private` selected initially.

@@ -523,6 +703,30 @@ Use `OpenAgentsWeb.UI` components, `to_form/2`, stable DOM IDs, and the

523 703
authenticated LiveView session. The browser calls the same context operation as
524 704
the API and never invokes forge filesystem code directly.
525 705
706
### Import from GitHub page
707
708
Add an authenticated `/repositories/import/github` route with:
709
710
- A bounded, paginated picker of GitHub repositories available through the
711
  retained grant.
712
- The GitHub owner, repository name, visibility, and default branch.
713
- A destination namespace that defaults to the matching GitHub user or
714
  organization namespace.
715
- An editable destination repository name.
716
- A `private` or `public` destination choice, with `private` selected initially.
717
- An explicit statement that the operation copies one snapshot and does not
718
  maintain synchronization.
719
- An **Import repository** button with pending and disabled states.
720
721
Use the existing GitHub adapter through a context operation. Extend its bounded
722
repository projection with the immutable repository and owner IDs, owner type,
723
default branch, permissions, and LFS warning inputs required by this contract.
724
The LiveView must never receive the retained GitHub token.
725
726
While an import runs, show the source full name, accepted head SHA when present,
727
current state, and a bounded failure code. Do not stream raw Git output to the
728
browser.
729
526 730
### Empty repository page
527 731
528 732
After creation, the repository page shows:

@@ -534,6 +738,10 @@ After creation, the repository page shows:

534 738
- A copy control with an accessible name.
535 739
- Links to Issues and Projects for the same repository.
536 740
741
For an imported repository, also show the GitHub source, accepted snapshot SHA,
742
completion time, and the statement **Imported once from GitHub**. Do not label
743
the repository as synced or mirrored.
744
537 745
Do not show push instructions until the repository is `ready`.
538 746
539 747
### Repository route

@@ -545,14 +753,15 @@ segment inventory. Add route tests proving that `/api`, `/auth`, `/admin`,

545 753
`/chat`, `/docs`, `/settings`, `/status`, and future declared product routes
546 754
cannot be interpreted as namespaces.
547 755
548
If that route contract cannot be proven without fragile ordering, use
549
`/code/{owner}/{repo}` and keep the Issue and Project routes unchanged. Resolve
550
this route decision before implementation begins.
756
If that route contract cannot be proven without fragile ordering, stop the
757
repository-home activation and revise this specification before using a
758
different public route.
551 759
552 760
## CLI product contract
553 761
554
The proposed binary name is `openagents`. Reserve `oa` as a possible later
555
alias; do not make scripts depend on it in the first release.
762
Publish the npm package as `@openagentsinc/cli` and expose the `openagents`
763
binary. Reserve `oa` as a possible later alias; do not make scripts depend on
764
it in the first release.
556 765
557 766
This CLI is the first-party repository-hosting client. It does not replace the
558 767
Pylon contributor runtime or absorb Pylon's agent-execution commands.

@@ -560,11 +769,20 @@ Pylon contributor runtime or absorb Pylon's agent-execution commands.

560 769
### Global behavior
561 770
562 771
```text
563
openagents [--endpoint <url>] [--json] [--no-color] <command>
772
openagents [--profile <name>] [--api-url <url>] [--json] [--no-color] <command>
564 773
```
565 774
566
- `--endpoint` defaults to `https://openagents.com`.
567
- `OPENAGENTS_ENDPOINT` provides the same setting for development and staging.
775
- The `production` profile targets `https://openagents.com` and is the default.
776
- The `staging` profile targets `https://staging.openagents.com`.
777
- The `local` profile targets `http://localhost:4000`.
778
- `--api-url` accepts a normalized custom API origin and overrides the selected
779
  profile for the current command.
780
- `OPENAGENTS_API_URL` provides the same per-process override for development,
781
  continuous integration, and end-to-end tests.
782
- `OPENAGENTS_PROFILE` selects a named profile when `--profile` is absent.
783
- Command flags take precedence over environment variables, which take
784
  precedence over persisted configuration, which takes precedence over the
785
  `production` default.
568 786
- `OPENAGENTS_TOKEN` provides a nonpersistent bearer token.
569 787
- `NO_COLOR` and `--no-color` disable ANSI output.
570 788
- `--json` emits one documented JSON value to stdout.

@@ -574,6 +792,17 @@ openagents [--endpoint <url>] [--json] [--no-color] <command>

574 792
575 793
The CLI must not send telemetry in the first release.
576 794
795
Accept `http` only for `localhost`, `127.0.0.1`, and `[::1]`. Require `https`
796
for every other API host. Reject credentials, query strings, fragments, and
797
non-root paths in API origins. Normalize the origin before any credential-store
798
lookup so production, staging, local, and custom services cannot share a token
799
by accident.
800
801
All automated CLI tests and local end-to-end tests must set
802
`OPENAGENTS_API_URL=http://localhost:4000` or pass the equivalent `--api-url`
803
flag. Tests must fail before a network call if the resolved origin is
804
`https://openagents.com` or `https://staging.openagents.com`.
805
577 806
### Authentication commands
578 807
579 808
```text

@@ -585,24 +814,24 @@ openagents auth setup-git --global

585 814
openagents auth logout
586 815
```
587 816
588
`auth status --json` reports the endpoint, authentication source, account login,
589
namespace, token expiry, and Git-helper state. It never reports a token or token
590
digest.
817
`auth status --json` reports the endpoint, authentication source, GitHub account
818
login and numeric ID, eligible GitHub namespaces, token expiry, and Git-helper
819
state. It never reports a token or token digest.
591 820
592 821
### Repository commands
593 822
594 823
```text
595 824
openagents repo create <name>
596 825
openagents repo create <owner>/<name>
826
openagents repo import <github-owner>/<github-repo>
597 827
openagents repo list
598 828
openagents repo view [<owner>/<name>]
599 829
openagents repo clone <owner>/<name> [<directory>]
600 830
```
601 831
602
`repo create <name>` targets the authenticated user's personal namespace.
832
`repo create <name>` targets the authenticated user's GitHub user namespace.
603 833
`repo create <owner>/<name>` succeeds only when the authenticated user can
604
create repositories in that namespace. Team namespace creation remains
605
unavailable until the server supports it.
834
create repositories in the matching GitHub organization namespace.
606 835
607 836
Create options:
608 837

@@ -627,6 +856,32 @@ Rules:

627 856
- `--remote` defaults to `origin` only when that remote is absent. If `origin`
628 857
  already points elsewhere, the command refuses to overwrite it.
629 858
859
Import options:
860
861
```text
862
--name <destination-name>
863
--namespace <github-owner>
864
--public
865
--private
866
--wait-timeout <duration>
867
```
868
869
Import rules:
870
871
- The source argument is a GitHub `owner/name`, not a URL.
872
- The destination namespace defaults to the GitHub source owner and must resolve
873
  to the same GitHub user or organization identity.
874
- `--namespace` can state that matching owner explicitly. It cannot copy the
875
  source into an unrelated namespace in the first release.
876
- `--name` defaults to the source repository name.
877
- The destination defaults to private. `--public` requires an explicit flag,
878
  including for a public source.
879
- The command submits one idempotent import, polls until `ready` or `failed`,
880
  and reports the accepted source head SHA.
881
- The command exits after the bounded `--wait-timeout` without canceling a
882
  durable import that still runs on the server.
883
- After success, the command states that later GitHub changes will not sync.
884
630 885
`repo list` supports `--namespace`, `--limit`, `--after`, and `--json`.
631 886
`repo view` and later repository-scoped commands infer the repository from the
632 887
`origin` remote when no argument is present. `-R, --repo <owner>/<name>`

@@ -708,7 +963,7 @@ Keep command handlers thin and compose these services:

708 963
709 964
| Service | Responsibility |
710 965
| --- | --- |
711
| `CliConfiguration` | Endpoint, output mode, timeouts, and environment inputs |
966
| `CliConfiguration` | Profile, API origin, output mode, timeouts, and environment inputs |
712 967
| `CredentialStore` | Secure token presence, read, write, and erase |
713 968
| `AuthClient` | Device authorization, status, and logout |
714 969
| `ForgeApiClient` | Authenticated repository API calls and schema decoding |

@@ -763,8 +1018,8 @@ The desired user experience is a single installer followed by

763 1018
764 1019
Evaluate these release steps in order:
765 1020
766
1. Run the CLI from the monorepo with Node 24 during development.
767
2. Publish an npm development preview if it improves dogfooding.
1021
1. Run `@openagentsinc/cli` from the monorepo with Node 24 during development.
1022
2. Publish an npm development preview after package and binary smoke tests pass.
768 1023
3. Produce checksum-verified standalone artifacts for macOS, Linux, and WSL.
769 1024
4. Serve a versioned installer from `openagents.com` only after artifact signing,
770 1025
   rollback, and update-channel behavior pass release tests.

@@ -782,6 +1037,8 @@ downloaded artifact.

782 1037
  repository for API or Git access.
783 1038
- Store API tokens, device codes, and poll secrets only as digests on the
784 1039
  server.
1040
- Keep the retained GitHub token inside the server adapter. Never return it to
1041
  the CLI, browser, provisioning row, import receipt, or Git remote.
785 1042
- Show a CLI token once and never include it in export, logs, telemetry,
786 1043
  receipts, exception messages, or JSON output.
787 1044
- Do not put credentials in clone URLs.

@@ -794,9 +1051,14 @@ downloaded artifact.

794 1051
- Return `404 Not Found` for a private repository when the caller must not learn
795 1052
  that it exists.
796 1053
- Audit repository creation, provisioning transitions, membership creation,
797
  token creation, and Git writes without recording repository content.
1054
  GitHub import transitions, token creation, and Git writes without recording
1055
  repository content.
798 1056
- Prevent repository creation from changing runtime configuration or deployment
799 1057
  targets.
1058
- Remove isolated import workspaces after success or failure through a bounded
1059
  recovery worker.
1060
- Never describe an imported repository as synchronized after the one accepted
1061
  snapshot completes.
800 1062
801 1063
## Test plan
802 1064

@@ -804,11 +1066,16 @@ downloaded artifact.

804 1066
805 1067
Add focused tests for:
806 1068
807
- Namespace claim, normalization, collision, and immutability.
1069
- GitHub user and organization namespace projection by immutable numeric ID.
1070
- GitHub login rename, alias routing, case normalization, and collision refusal.
808 1071
- Repository name and default-branch validation.
809
- Atomic repository, owner-membership, and outbox creation.
1072
- Atomic repository, owner-membership, optional import, and outbox creation.
810 1073
- Idempotent repeat requests and mismatched idempotency keys.
811
- Personal namespace authority and refusal of arbitrary owner creation.
1074
- GitHub user namespace authority, organization admin authority, stale
1075
  membership refresh, and refusal of arbitrary owner creation.
1076
- Missing GitHub connection, missing `read:org`, source repository refusal, and
1077
  private source authorization.
1078
- Import request and status projections without credentials or raw Git output.
812 1079
- Public, private, member, nonmember, banned-user, suspended, failed, and
813 1080
  missing repository reads.
814 1081
- `201`, `202`, `401`, `403`, `404`, `409`, `422`, and `503` behavior.

@@ -827,6 +1094,13 @@ Add tests for:

827 1094
- A crash after each provisioning transition followed by convergence.
828 1095
- Duplicate outbox delivery.
829 1096
- WAL initialization before `ready`.
1097
- Frozen GitHub branch and tag refs, source ref-digest verification, and an
1098
  update that lands on GitHub after the accepted snapshot.
1099
- Import of public and private GitHub repositories.
1100
- Import failure, retry, interruption, timeout, and isolated-workspace cleanup.
1101
- LFS detection and the required pointer-only warning.
1102
- Proof that neither GitHub nor OpenAgents receives a synchronization write
1103
  after import completion.
830 1104
- Bare-cache deletion followed by reconstruction.
831 1105
- Two repositories with the same name in different namespaces.
832 1106
- Anonymous clone of a public repository.

@@ -843,8 +1117,10 @@ Add tests for:

843 1117
Add tests for stable DOM IDs and outcomes:
844 1118
845 1119
- Repository list, empty state, and pagination.
846
- Namespace enrollment state.
1120
- GitHub user and organization namespace selection.
847 1121
- Create form validation and submission.
1122
- GitHub repository picker, import submission, progress, warning, and failure
1123
  states.
848 1124
- Private visibility as the default.
849 1125
- Provisioning, ready, and failed states.
850 1126
- Clone instructions only after readiness.

@@ -860,6 +1136,8 @@ Use deterministic Effect layers and fake clocks for:

860 1136
  interruption.
861 1137
- Token redaction from human output, JSON, errors, logs, and snapshots.
862 1138
- Repository create `201` and `202` flows.
1139
- Repository import `201`, `202`, failure, timeout, and idempotent retry flows.
1140
- Matching GitHub destination namespace enforcement.
863 1141
- Idempotent retry after a connection failure.
864 1142
- Remote inference and refusal of unadmitted hosts.
865 1143
- Refusal to overwrite an existing `origin` remote.

@@ -871,72 +1149,88 @@ Use deterministic Effect layers and fake clocks for:

871 1149
Run an end-to-end suite against a disposable Phoenix server, PostgreSQL
872 1150
database, forge storage directory, and Git checkout. The suite must create a
873 1151
repository through the CLI, push a commit with standard Git, clone it into a
874
second directory, and verify the exact commit SHA.
1152
second directory, and verify the exact commit SHA. A second case must import a
1153
GitHub fixture repository, verify every accepted branch and tag, change the
1154
GitHub fixture after acceptance, and prove that the OpenAgents repository does
1155
not synchronize that later change.
875 1156
876 1157
## Delivery sequence
877 1158
878 1159
### Phase 0: Close design decisions
879 1160
880
1. Approve the CLI binary and package names.
881
2. Approve the personal namespace enrollment behavior.
882
3. Approve the canonical browser and Git URL shapes.
883
4. Select the initial CLI distribution format.
884
5. Record the repository lifecycle and authority split in `INVARIANTS.md`.
1161
1. Approve the canonical browser and Git URL shapes.
1162
2. Confirm the GitHub `read:org` consent and reconnection migration.
1163
3. Record the initial npm-only distribution boundary and the later standalone
1164
   artifact gate.
1165
4. Record the GitHub namespace, one-time import, repository lifecycle, and
1166
   authority split in `INVARIANTS.md`.
885 1167
886 1168
### Phase 1: Add namespace and repository authority
887 1169
888 1170
1. Generate migrations with `mix ecto.gen.migration`.
889
2. Add namespace ownership and repository lifecycle schemas.
890
3. Backfill `OpenAgentsInc/openagents.com` into an explicit namespace.
891
4. Add transactionally created owner membership and provisioning outbox rows.
1171
2. Add GitHub-backed namespace, namespace-alias, repository-import, and
1172
   repository lifecycle schemas.
1173
3. Backfill `OpenAgentsInc/openagents.com` into an explicit GitHub organization
1174
   namespace identified by its numeric GitHub ID.
1175
4. Add transactionally created owner membership, optional import, and
1176
   provisioning outbox rows.
892 1177
5. Rehearse the migration down and up against populated fixtures.
893 1178
894
### Phase 2: Add create, list, and view APIs
1179
### Phase 2: Add create, import, list, and view APIs
895 1180
896 1181
1. Add failing controller tests for `POST /api/v3/user/repos`.
897
2. Implement the context operation, controller, JSON projection, and route.
898
3. Add authenticated repository list and optional-auth repository view.
899
4. Add idempotency, cursor, error-envelope, and route-authority tests.
900
5. Publish the initial client contract artifact.
1182
2. Add failing controller tests for the user and organization import routes.
1183
3. Implement the context operations, controllers, JSON projections, and routes.
1184
4. Add authenticated repository list, import status, and optional-auth
1185
   repository view.
1186
5. Add idempotency, cursor, error-envelope, and route-authority tests.
1187
6. Publish the initial client contract artifact.
901 1188
902 1189
### Phase 3: Provision Git repositories
903 1190
904 1191
1. Make the hosted repository inventory database-backed.
905 1192
2. Change storage keys and Git paths to include stable repository identity.
906 1193
3. Add the idempotent provisioning worker and recovery scan.
907
4. Scope upload-pack and receive-pack through repository policy.
908
5. Preserve the deployment allowlist as a separate operator control.
909
6. Prove create, push, cache loss, reconstruction, fetch, and clone locally.
1194
4. Add the one-time GitHub importer with frozen refs, bounded retries, secure
1195
   credential delivery, and temporary-workspace recovery.
1196
5. Scope upload-pack and receive-pack through repository policy.
1197
6. Preserve the deployment allowlist as a separate operator control.
1198
7. Prove create, import, push, cache loss, reconstruction, fetch, and clone
1199
   locally.
910 1200
911 1201
### Phase 4: Add CLI authentication
912 1202
913 1203
1. Add device authorization records and endpoints.
914
2. Add the authenticated browser approval page.
915
3. Add one-time PAT delivery and expiry behavior.
916
4. Implement the CLI credential store and Git credential helper.
917
5. Pass secret-handling and headless-agent tests.
1204
2. Add GitHub organization projection and the proposed `read:org` consent
1205
   migration.
1206
3. Add the authenticated browser approval page.
1207
4. Add one-time PAT delivery and expiry behavior.
1208
5. Implement the CLI credential store and Git credential helper.
1209
6. Pass secret-handling and headless-agent tests.
918 1210
919 1211
### Phase 5: Build the Effect CLI
920 1212
921 1213
1. Create `packages/openagents-cli` in the monorepo.
922 1214
2. Pin the Phoenix-owned contract artifact.
923
3. Implement `auth` and `repo` command groups through Effect services.
1215
3. Implement `auth`, `repo create`, and `repo import` through Effect services.
924 1216
4. Add human output, JSON output, exit-code, signal, and redaction tests.
925 1217
5. Run the cross-repository disposable end-to-end suite.
926 1218
927 1219
### Phase 6: Add the browser interface
928 1220
929 1221
1. Replace the hard-coded dashboard repository card with the scoped list.
930
2. Add namespace enrollment and repository creation pages.
931
3. Add provisioning and empty-repository states.
932
4. Run accessibility, keyboard, responsive, and browser checks.
1222
2. Add GitHub namespace selection and repository creation pages.
1223
3. Add GitHub repository selection and one-time import pages.
1224
4. Add provisioning, import, and empty-repository states.
1225
5. Run accessibility, keyboard, responsive, and browser checks.
933 1226
934 1227
### Phase 7: Stage and release
935 1228
936 1229
1. Run `mix precommit` and the owned exact-SHA gate in `openagents.com`.
937 1230
2. Run `pnpm run check` in the CLI monorepo.
938 1231
3. Deploy to an isolated staging environment only after Gate 12 permits it.
939
4. Create, push, clone, revoke, retry, and recover on one staging candidate.
1232
4. Create, import, push, clone, revoke, retry, and recover on one staging
1233
   candidate.
940 1234
5. Scan the complete log window for tokens, clone credentials, repository
941 1235
   content, and private paths.
942 1236
6. Publish the CLI only after the server candidate and client contract digest

@@ -944,9 +1238,11 @@ second directory, and verify the exact commit SHA.

944 1238
945 1239
## Acceptance criteria
946 1240
947
The first repository-creation slice is complete when:
1241
The first repository and import slice is complete when:
948 1242
949
- An authenticated user has one stable personal namespace.
1243
- An authenticated user receives the GitHub user namespace identified by the
1244
  same numeric GitHub user ID and sees eligible organization namespaces
1245
  identified by their numeric GitHub organization IDs.
950 1246
- The browser and CLI create the same repository resource through the same
951 1247
  context policy.
952 1248
- A successful command returns only after the repository is ready, or reports a

@@ -954,6 +1250,12 @@ The first repository-creation slice is complete when:

954 1250
- The creator receives an `owner` membership in the same database transaction
955 1251
  as the repository row.
956 1252
- A repeat request with the same idempotency key cannot create a duplicate.
1253
- The browser and CLI can import an authorized GitHub repository's accepted
1254
  history, branches, and tags into the matching GitHub namespace.
1255
- An imported repository records its source and exact accepted snapshot without
1256
  retaining a GitHub credential.
1257
- A GitHub commit created after import acceptance does not appear in OpenAgents
1258
  without a new explicit future import or synchronization feature.
957 1259
- A public repository clones anonymously.
958 1260
- A private repository is hidden from a nonmember.
959 1261
- A permitted user can push and a read-only or unrelated user cannot.

@@ -963,23 +1265,23 @@ The first repository-creation slice is complete when:

963 1265
  argv, logs, JSON, or a clone URL.
964 1266
- `openagents repo create`, standard `git push`, and `openagents repo clone`
965 1267
  complete an exact-SHA end-to-end test.
1268
- `openagents repo import` completes a second exact-ref end-to-end test for a
1269
  GitHub fixture.
966 1270
- Both repositories pass their required local gates at the exact delivered
967 1271
  revisions.
968 1272
969
## Decisions required before implementation
970
971
The owner must resolve these items before Phase 1 begins:
972
973
1. **Binary name:** Use `openagents`, `oa`, or both.
974
2. **Namespace enrollment:** Claim the suggested GitHub-login slug
975
   automatically, or require a one-time browser confirmation for every user.
976
3. **Repository home route:** Use `/{owner}/{repo}` with a reserved-route
977
   contract, or use `/code/{owner}/{repo}`.
978
4. **Distribution:** Start with npm, standalone artifacts, or both.
979
5. **Effect command parser:** Use a parser compatible with the pinned Effect
980
   version, or keep parsing local while all effects remain in Effect services.
981
982
This document recommends `openagents`, automatic claim with an explicit
983
collision flow, `/{owner}/{repo}`, a short npm dogfood phase followed by signed
984
standalone artifacts, and Effect TypeScript with a parser selected by a bounded
985
spike.
1273
## Implementation decisions recorded
1274
1275
This document records these implementation decisions:
1276
1277
- GitHub user and organization identity is the first-release namespace
1278
  authority.
1279
- The package is `@openagentsinc/cli`, and the binary is `openagents`.
1280
- The CLI uses Effect TypeScript with `effect/unstable/cli`.
1281
- The CLI includes production, staging, and local API profiles and accepts a
1282
  validated custom API origin.
1283
- The browser uses `/{owner}/{repo}` after the reserved-route proof passes.
1284
- GitHub OAuth requests `read:org` with `repo`; an existing connection that
1285
  lacks a required scope must reconnect.
1286
- The first distribution is an npm dogfood release. Signed standalone artifacts
1287
  require their separate release gate.
docs/repository-creation-cli-implementation-roadmap.md added +479

@@ -0,0 +1,479 @@

1
# Repository creation and CLI implementation roadmap
2
3
Date: 2026-08-20
4
5
Status: Implementation complete; local end-to-end and staging verification
6
remain
7
8
## Outcome
9
10
Implement repository creation, one-time GitHub import, Git smart HTTP, browser
11
management, and the `@openagentsinc/cli` package as one tested product across
12
the `openagents.com` Phoenix repository and the `openagents` Effect monorepo.
13
14
The product specification remains the contract:
15
[Repository creation, GitHub import, and OpenAgents CLI specification](repository-creation-and-openagents-cli-spec.md).
16
This roadmap records implementation order, owning files, dependencies, tests,
17
and release evidence. Update the status ledger as each work packet lands.
18
19
## Recorded decisions
20
21
- Publish the npm package as `@openagentsinc/cli`.
22
- Expose the `openagents` binary. Do not ship an `oa` alias in the first
23
  release.
24
- Use Effect TypeScript and the monorepo-pinned Effect 4 release.
25
- Use `effect/unstable/cli` for argument and flag parsing.
26
- Default to the `production` profile at `https://openagents.com`.
27
- Provide the `staging` profile at `https://staging.openagents.com`.
28
- Provide the `local` profile at `http://localhost:4000`.
29
- Support `--api-url`, `OPENAGENTS_API_URL`, `--profile`, and
30
  `OPENAGENTS_PROFILE` for explicit endpoint selection.
31
- Scope stored credentials to the normalized API origin.
32
- Pin automated and local end-to-end tests to `http://localhost:4000` and fail
33
  closed if a test resolves a production or staging origin.
34
- Use GitHub numeric user and organization IDs as namespace identity. Use the
35
  current GitHub login as a mutable display and route projection.
36
- Request `read:org` in addition to the existing `repo` grant so OpenAgents can
37
  project organization membership and roles.
38
- Import one accepted GitHub ref snapshot. Do not install a webhook or schedule
39
  later synchronization.
40
- Use `/{owner}/{repo}` for the repository home after executable reserved-route
41
  tests prove that product routes cannot become namespaces.
42
- Start distribution through npm. Gate standalone artifacts and an installer as
43
  a later release packet.
44
45
## Definition of done
46
47
All of these statements require direct evidence before this roadmap can move to
48
`Complete`:
49
50
- A GitHub-authenticated user receives a user namespace keyed by the same
51
  numeric GitHub user ID.
52
- An eligible GitHub organization appears as a namespace keyed by the same
53
  numeric GitHub organization ID and current login.
54
- The browser and CLI create private or public repositories through the same
55
  Phoenix context operation.
56
- The browser and CLI import an authorized GitHub repository once, including
57
  its accepted branches, tags, and reachable Git objects.
58
- A later GitHub update does not change the imported OpenAgents repository.
59
- The repository API returns stable JSON and error envelopes from a versioned
60
  Phoenix-owned contract artifact.
61
- Public repositories support anonymous clone and fetch.
62
- Private repositories remain hidden from unrelated users.
63
- Repository members with write roles can push. Read-only and unrelated users
64
  cannot push.
65
- Git storage uses a stable repository UUID and survives deletion of the local
66
  bare cache through WAL reconstruction.
67
- `@openagentsinc/cli` targets production by default and can explicitly target
68
  staging, local, or a validated custom HTTPS API origin.
69
- CLI credentials, GitHub credentials, device codes, and clone URLs never
70
  appear in command arguments, logs, JSON output, repository configuration, or
71
  durable import records.
72
- `mix precommit` passes in `openagents.com`.
73
- `pnpm run check` passes in `openagents`.
74
- A disposable local test at `http://localhost:4000` proves create, push,
75
  clone, exact commit SHA, import, exact refs, and no later synchronization.
76
77
## Implementation dependency graph
78
79
```mermaid
80
flowchart TD
81
  A["Contract and invariants"] --> B["Database authority"]
82
  B --> C["GitHub namespace projection"]
83
  B --> D["Repository API"]
84
  C --> D
85
  D --> E["Provisioning and Git HTTP"]
86
  C --> F["One-time GitHub importer"]
87
  E --> F
88
  D --> G["CLI contract artifact"]
89
  G --> H["Effect CLI"]
90
  D --> I["Browser repository UI"]
91
  F --> I
92
  F --> J["Local end-to-end proof"]
93
  H --> J
94
  I --> J
95
```
96
97
The schema, migrations, route table, generated contract artifact, package
98
manifest, lockfile, and behavior-contract registry are shared integration
99
points. Change them serially under one integration owner.
100
101
## Work packet 0: Record the contract and safety boundaries
102
103
**Owner:** `openagents.com`, with a matching invariant and behavior-contract
104
update in `openagents` when the CLI package begins.
105
106
1. Update `docs/repository-creation-and-openagents-cli-spec.md` with the package
107
   name and endpoint profiles.
108
2. Add this roadmap and keep its status ledger current.
109
3. Add repository lifecycle, namespace, import snapshot, and credential
110
   boundaries to `openagents.com/INVARIANTS.md` before server activation.
111
4. Add a pending then enforced CLI behavior contract in the monorepo for the
112
   user requirement that the CLI targets production by default and supports
113
   local, staging, and custom API origins.
114
5. Record the `@openagentsinc/cli` claim and worktree in the accepted work
115
   packet before monorepo mutation.
116
117
**Evidence:** Documentation checks, invariant tests, behavior-contract tests,
118
and a clean diff review that finds no production claim ahead of implementation.
119
120
## Work packet 1: Add namespace and repository lifecycle persistence
121
122
**Owner:** `openagents.com`.
123
124
Generate migrations with `mix ecto.gen.migration`. Do not hand-author migration
125
timestamps.
126
127
1. Add `namespaces` with provider identity, current slug, namespace kind,
128
   refresh time, state, and optional local user owner.
129
2. Add `namespace_aliases` for previous GitHub logins. Allow aliases to resolve
130
   routes, but never use them as mutation authority.
131
3. Backfill a namespace for `OpenAgentsInc/openagents.com` using an operator-
132
   supplied or migration-safe GitHub organization ID. Refuse deployment if the
133
   required production identity is absent or inconsistent.
134
4. Add `namespace_id`, `description`, `lifecycle_state`, `provisioning_kind`,
135
   `provision_error_code`, `storage_key`, `created_by_user_id`, and `ready_at`
136
   to `repositories`.
137
5. Replace path uniqueness with `{namespace_id, name_key}` while retaining a
138
   bounded compatibility projection for `owner` during the migration.
139
6. Add `repository_imports` with immutable source IDs, bounded source
140
   projections, accepted ref digest, head SHA, state, attempts, error code, and
141
   timing.
142
7. Add `repository_provisioning_outbox` with operation kind, repository ID,
143
   import ID, idempotency identity, attempt state, retry time, and bounded
144
   failure code.
145
8. Add a request-idempotency table keyed by principal, operation, key, and
146
   normalized request digest.
147
9. Add Ecto schemas under `lib/openagents/repositories/` and associations on
148
   `Repository`.
149
10. Rework `OpenAgents.Repositories` around transactionally created repository,
150
    owner membership, optional import, idempotency receipt, and outbox rows.
151
152
**Tests:** `test/openagents/repositories_test.exs`, migration lineage tests,
153
constraint tests, rename/alias tests, collision tests, atomic rollback tests,
154
and migration up/down rehearsal against populated fixtures.
155
156
## Work packet 2: Project GitHub namespaces and import sources
157
158
**Owner:** `openagents.com`.
159
160
1. Extend `OpenAgents.GitHub` with bounded Req adapters for:
161
   - the authenticated user,
162
   - active organization memberships,
163
   - one repository by `owner/name`,
164
   - paginated repositories available to the user,
165
   - repository permissions,
166
   - branch and tag refs,
167
   - Git LFS detection inputs.
168
2. Decode provider responses into internal structs. Do not pass raw provider
169
   maps into contexts or LiveViews.
170
3. Add a namespace projection service that upserts the user namespace at GitHub
171
   sign-in and refreshes organization namespaces before organization writes.
172
4. Treat GitHub numeric IDs as authority and logins as mutable projections.
173
5. Require an active organization membership and the first-release `admin`
174
   create policy before an organization destination mutation.
175
6. Require source read access for import. Do not require source-repository admin
176
   access merely to make a one-time copy.
177
7. Add `read:org` to the OAuth request and exact granted-scope validation.
178
8. Make existing connected users reconnect when their retained grant lacks a
179
   required scope. Do not rewrite stored scope metadata.
180
9. Return stable failures such as `github_connection_required`,
181
   `github_scope_required`, `namespace_not_allowed`, and
182
   `source_repository_not_accessible`.
183
184
**Tests:** Req fakes for pagination, public and private repositories, renamed
185
logins, private organization membership, stale membership, missing scope,
186
provider failure, rate limiting, malformed JSON, and token redaction.
187
188
## Work packet 3: Implement the repository REST API
189
190
**Owner:** `openagents.com`.
191
192
Add thin controllers and keep policy in `OpenAgents.Repositories` services.
193
194
1. Add authenticated routes:
195
   - `POST /api/v3/user/repos`
196
   - `POST /api/v3/orgs/{org}/repos`
197
   - `POST /api/v3/user/repos/imports`
198
   - `POST /api/v3/orgs/{org}/repos/imports`
199
   - `GET /api/v3/user/repos`
200
   - `GET /api/v3/repository-imports/{id}`
201
2. Add optional-auth `GET /api/v3/repos/{owner}/{repo}`.
202
3. Add an optional bearer-token pipeline that permits anonymous public reads
203
   but authenticates a supplied PAT before private reads.
204
4. Add explicit route-authority entries for authenticated `GET` routes because
205
   the current classifier treats `/api/v3` reads as public.
206
5. Enforce `forge:write`, user status, namespace authority, visibility,
207
   lifecycle state, quotas, names, branches, and idempotency before persistence.
208
6. Return `201 Created` when bounded provisioning finishes and `202 Accepted`
209
   when durable work continues.
210
7. Return stable error envelopes and conceal private repositories with `404`.
211
8. Add opaque cursor pagination with a maximum page size of 100.
212
9. Build JSON projections in `RepositoryJSON` and `RepositoryImportJSON`; do not
213
   assemble public JSON in context modules.
214
10. Add a versioned repository API artifact under `priv/api-contracts/` and an
215
    authenticated-independent route or build task that exposes its exact bytes.
216
217
**Tests:** New controller tests for status, shape, policy, private concealment,
218
pagination, idempotency, conflict, organization refresh, imported source
219
validation, `201`/`202`, and every stable error code. Extend
220
`test/openagents_web/route_authority_test.exs`.
221
222
## Work packet 4: Make repository provisioning durable
223
224
**Owner:** `openagents.com`.
225
226
1. Add an OTP provisioner under `OpenAgents.Repositories.Provisioner` and start
227
   it through the application supervision tree with `start_supervised!/1` in
228
   tests.
229
2. Claim outbox rows with database locking, bounded attempts, and retry times.
230
3. Create an empty WAL index using `repository.storage_key` before declaring a
231
   repository ready.
232
4. Change `OpenAgents.Forge.Repos`, `WAL`, `Sync`, `Pushes`, `Browse`, and Git
233
   HTTP resolution to use stable repository storage keys instead of the
234
   process-wide repository-name allowlist.
235
5. Keep the deployable repository allowlist separate. Creating a repository
236
   must never make it buildable or deployable.
237
6. Make each provisioning transition idempotent and crash-recoverable.
238
7. Bound and redact operational errors before persistence or response.
239
8. Add a recovery scan for stranded `provisioning` and retryable `failed`
240
   records.
241
242
**Tests:** Crash after each transition, duplicate outbox delivery, CAS conflict,
243
cache deletion and reconstruction, two same-named repositories in different
244
namespaces, storage-key path containment, and proof that repository creation
245
does not change deployment targets.
246
247
## Work packet 5: Implement one-time GitHub import
248
249
**Owner:** `openagents.com`.
250
251
1. Resolve and persist the immutable GitHub source repository and owner IDs,
252
   default branch, permissions, branch and tag map, ref digest, and default head
253
   before the database transaction.
254
2. Fetch the accepted refs into a unique temporary bare repository.
255
3. Supply the retained GitHub token through a server-owned Git credential
256
   callback or askpass boundary. Never place it in a URL, argv, environment
257
   dump, Git config, log, or import record.
258
4. Verify that the fetched refs match the accepted digest. Fail with a stable
259
   source-change code if the snapshot cannot be reproduced.
260
5. Convert the imported objects and refs into the destination WAL and
261
   materialize the destination cache.
262
6. Set the symbolic default branch, accepted head SHA, import completion time,
263
   and repository readiness atomically at the final database transition.
264
7. Remove temporary workspaces after success, expected failure, interruption,
265
   or recovery cleanup.
266
8. Detect LFS pointer use and return the required pointer-only warning.
267
9. Install no webhook and schedule no later provider read or write.
268
269
**Tests:** Public and private source fixtures, multiple branches and annotated
270
tags, empty source, submodule pointers, LFS pointers, source changes during
271
acceptance, interruption, retry, cleanup, exact ref digest, later source update,
272
and assertions that neither side receives a synchronization call.
273
274
## Work packet 6: Authorize Git smart HTTP by repository
275
276
**Owner:** `openagents.com`.
277
278
1. Change the Git route to `/git/{owner}/{repo}.git` while keeping the temporary
279
   `/git/openagents.com.git` compatibility route required by the forge cutover
280
   contract.
281
2. Allow anonymous upload-pack only for a ready public repository.
282
3. Accept `oa_pat_...` through HTTP Basic password input for authenticated Git
283
   operations and resolve it to a user principal.
284
4. Check repository membership and role before private upload-pack or any
285
   receive-pack operation.
286
5. Preserve paired-machine and operator credentials only for their documented
287
   operational lanes. Do not let them bypass repository resolution.
288
6. Return indistinguishable `404` responses for private or missing repository
289
   reads where concealment applies.
290
7. Keep request bodies bounded, argv-only Git invocation, WAL persistence before
291
   push acknowledgment, and exact rollback on WAL failure.
292
293
**Tests:** Anonymous public clone, anonymous private refusal, member private
294
clone, writer push, viewer refusal, unrelated-user refusal, banned-user refusal,
295
failed/provisioning repository refusal, compatibility route, protocol v2, and
296
credential-redaction scans.
297
298
## Work packet 7: Add CLI device authorization
299
300
**Owner:** `openagents.com` server contract and browser UI.
301
302
1. Add digested, expiring device authorization records with one-time claim and
303
   polling limits.
304
2. Add public create and poll endpoints for a secret device code.
305
3. Add an authenticated, CSRF-protected browser approval page.
306
4. Mint one scoped `oa_pat_...` after approval and return its plaintext exactly
307
   once.
308
5. Return stable pending, slow-down, denied, expired, claimed, and approved
309
   states without enabling enumeration.
310
6. Add `cache-control: no-store` on every authorization response.
311
312
**Tests:** expiry, denial, one-time delivery, polling pace, collision, CSRF,
313
revoked user, banned user, concurrent claims, token digest storage, and log
314
redaction.
315
316
## Work packet 8: Build the repository browser interface
317
318
**Owner:** `openagents.com`.
319
320
1. Replace the hard-coded repository card with a scoped repository stream.
321
2. Add authenticated repository list, empty, pagination, provisioning, failed,
322
   and ready states.
323
3. Add `/repositories/new` with GitHub user and eligible organization namespace
324
   selection, name, description, visibility, and default branch.
325
4. Add `/repositories/import/github` with a paginated GitHub repository picker,
326
   destination name, matching namespace, visibility, LFS warning, and explicit
327
   one-time import copy.
328
5. Add `/{owner}/{repo}` repository home, clone controls, import receipt, and
329
   links to code, Issues, and Projects.
330
6. Put the dynamic repository route after every reserved route and maintain an
331
   executable reserved-segment inventory.
332
7. Use `OpenAgentsWeb.UI` primitives, Basecoat imports already required by the
333
   surface, stable DOM IDs, keyboard operation, and responsive layouts.
334
8. Keep GitHub tokens and raw Git output outside LiveView assigns.
335
336
**Tests:** LiveView forms and outcomes by DOM ID, namespace selection, private
337
default, import picker pagination, import progress/failure, ready-only clone
338
instructions, reserved routes, accessibility, and browser smoke coverage.
339
340
## Work packet 9: Scaffold `@openagentsinc/cli`
341
342
**Owner:** `openagents` monorepo in a fresh worktree from current
343
`origin/main`.
344
345
1. Create `packages/openagents-cli/package.json` with name
346
   `@openagentsinc/cli`, ESM exports, `openagents` bin, Node 24 engine, build,
347
   test, typecheck, lint, and package verification scripts.
348
2. Add `tsconfig.json` using the monorepo TypeScript and Effect language service
349
   conventions.
350
3. Add the package to the existing workspace through `packages/*`; do not add a
351
   second workspace mechanism.
352
4. Pin `effect`, `@effect/platform-node`, and test dependencies through the root
353
   catalog. Update the shared lockfile once under the integration owner.
354
5. Add Effect Schema decoders for the Phoenix-owned contract artifact and pin
355
   its version and SHA-256 digest.
356
6. Define services for configuration, credentials, HTTP, authentication,
357
   repository operations, Git, credential helper, browser launch, and output.
358
7. Model expected failures with `Schema.TaggedErrorClass` and map exit codes in
359
   one exhaustive boundary.
360
8. Add the owner-stated endpoint behavior to the package behavior-contract
361
   registry and enforce it with tests.
362
363
**Tests:** package entrypoint, `--help`, `--version`, invalid input, contract
364
digest, schema decoding, packaging contents, Node 24 execution, and no
365
production network access from tests.
366
367
## Work packet 10: Implement CLI configuration and authentication
368
369
**Owner:** `openagents`.
370
371
1. Resolve configuration in this order: command flags, environment variables,
372
   persisted profile, then production default.
373
2. Validate and normalize API origins. Permit plain HTTP only on loopback hosts.
374
3. Keep credentials isolated by normalized origin.
375
4. Use `Config` and `ConfigProvider` for environment input; do not read
376
   `process.env` in application services.
377
5. Implement `auth login`, browser launch, device polling with `Schedule`, token
378
   stdin, status, logout, and Git helper setup.
379
6. Use an admitted operating-system credential adapter for attended use. Use
380
   `OPENAGENTS_TOKEN` without persistence for headless use.
381
7. Implement the exact Git credential-helper protocol without printing the
382
   token.
383
8. Redact authorization headers, tokens, device secrets, filesystem paths, and
384
   raw response bodies from every output mode.
385
386
**Tests:** production, staging, local, custom HTTPS, loopback HTTP, precedence,
387
origin-scoped credentials, malformed origins, fake browser, fake clock polling,
388
headless behavior, token stdin, JSON output, signals, and secret tripwires.
389
390
## Work packet 11: Implement CLI repository commands
391
392
**Owner:** `openagents`.
393
394
1. Implement `repo create`, including visibility, description, branch, source
395
   directory, remote name, idempotency key, `201`/`202`, and polling.
396
2. Implement `repo import`, including matching namespace, destination name,
397
   visibility, timeout, source head receipt, and no-sync notice.
398
3. Implement cursor-based `repo list` and `repo view`.
399
4. Implement `repo clone` using the server-provided clone URL.
400
5. Implement safe repository inference from an admitted remote and explicit
401
   `-R` override.
402
6. Refuse to overwrite an existing unrelated remote.
403
7. Execute Git with argv arrays, bounded captured output, cancellation, and no
404
   credential in the remote URL.
405
8. Keep human progress on stderr when JSON owns stdout.
406
407
**Tests:** all command options, mutual exclusions, error and exit-code mapping,
408
idempotent retry, timeout without server cancellation, remote inference, remote
409
collision, Git failure, JSON snapshots, interruption, and token redaction.
410
411
## Work packet 12: Verify locally across both repositories
412
413
**Owner:** cross-repository integration.
414
415
1. Start an isolated Phoenix server at `http://localhost:4000` with local WAL
416
   storage, a disposable database, deterministic GitHub fakes, and forge
417
   deployment disabled.
418
2. Create a fixture GitHub user, organization, retained grant, and source bare
419
   repository without exposing a token to the test process output.
420
3. Run the CLI with `OPENAGENTS_API_URL=http://localhost:4000`.
421
4. Authenticate with a disposable PAT or complete the device flow.
422
5. Create a repository, configure Git credentials, push a commit, clone it, and
423
   compare the exact commit SHA.
424
6. Import a fixture with multiple branches and tags, compare the complete ref
425
   map, update the source, and prove the destination remains unchanged.
426
7. Prove anonymous public clone, private concealment, writer push, and viewer
427
   refusal.
428
8. Delete the destination bare cache, reconstruct it from WAL, clone again, and
429
   compare refs.
430
9. Scan server and CLI logs plus Git config for fixture secrets and credentialed
431
   URLs.
432
10. Run `mix precommit` and `pnpm run check` from the exact delivered revisions.
433
434
**Evidence:** Store a bounded test receipt with repository revisions, contract
435
digest, API origin, exact commit and ref digests, test counts, and secret-scan
436
result. Do not store tokens, local absolute paths, or repository content.
437
438
## Work packet 13: Stage and release
439
440
**Owner:** release operator after local completion.
441
442
1. Deploy the server to `https://staging.openagents.com` through the admitted
443
   staging gate.
444
2. Run the CLI with `--profile staging` and repeat create, import, clone, push,
445
   refusal, recovery, and redaction checks.
446
3. Confirm migrations, OAuth reconnect copy, GitHub rate behavior, Cloud Storage
447
   WAL durability, and private repository concealment.
448
4. Pack `@openagentsinc/cli`, inspect the tarball, install it into an empty
449
   prefix, and run its command matrix.
450
5. Publish an npm preview only after the server and CLI contract digests match.
451
6. Keep standalone binaries, the installer, self-update, pull requests,
452
   mirroring, deletion, and SSH transport in later gated packets.
453
454
## Status ledger
455
456
| Packet | State | Evidence |
457
| --- | --- | --- |
458
| 0. Contract and safety boundaries | Complete | `INVARIANTS.md`, Phoenix contract artifact, CLI behavior contract |
459
| 1. Namespace and lifecycle persistence | Complete | Lifecycle migrations and `repository_lifecycle_test.exs` |
460
| 2. GitHub namespace projection | Complete | GitHub adapter, exact `repo` and `read:org` scopes, projection tests |
461
| 3. Repository REST API | Complete | Repository controllers, JSON contract, idempotency and pagination tests |
462
| 4. Durable provisioning | Complete | Provisioning outbox, reclaimable worker, and cache reconstruction tests |
463
| 5. One-time GitHub import | Complete | Frozen refs, Git bundle WAL entry, cache-loss and no-later-sync tests |
464
| 6. Repository Git HTTP authorization | Complete | Public reads, PAT writes, role refusal, and legacy-route tests |
465
| 7. CLI device authorization | Complete | One-time device-code context, API, browser approval, and polling tests |
466
| 8. Repository browser interface | Complete | List, create, import, empty, failed, private, and code-route LiveView tests |
467
| 9. CLI package scaffold | Complete | `@openagentsinc/cli`, Effect 4 command graph, build, and package inspection |
468
| 10. CLI configuration and authentication | Complete | Profiles, custom origins, OS credential store, and Git helper tests |
469
| 11. CLI repository commands | Complete | Create, import, list, view, clone, source remote, inference, and refusal tests |
470
| 12. Local cross-repository verification | In progress | Contract digests and focused suites pass; disposable CLI-to-server create, push, clone, and import proof remains |
471
| 13. Staging and release | Not started | — |
472
473
## Completion audit
474
475
Before changing this roadmap to `Complete`, inspect current files and command
476
output for every definition-of-done item. A passing unit test does not prove an
477
end-to-end behavior unless the test crosses the same browser or CLI, API,
478
database, GitHub adapter, WAL, and Git boundaries as the requirement. Record
479
missing or indirect evidence as incomplete and continue implementation.

This page updates live while a promote is in flight · changelog