Skip to content

feat(metadata): repository descriptions and owner profiles - #97

Open
jonasgrosch wants to merge 1 commit into
tobi:mainfrom
conxai-technologies:conxai/metadata
Open

jonasgrosch wants to merge 1 commit into
tobi:mainfrom
conxai-technologies:conxai/metadata

Conversation

@jonasgrosch

Copy link
Copy Markdown

Deployments that use opaque ids (e.g. UUIDs) as owners/repositories have nowhere to keep a human-readable name, so the home page and repository lists are unreadable. Two small control objects, shaped like policy.json and kept off the WAL:

  • Repository description: repos/<o>/<r>/description.json {"description"}. GET|PUT|DELETE /{o}/{r}/api[-browser]/description; PUT /{o}/{r}?description=… at create time; walgit repo describe <repo> [--set TEXT|--clear].
  • Owner profile: owners/<o>/profile.json {"display_name","description"}. GET|PUT|DELETE /api[-browser]/v1/owners/{owner}. At the bucket root because repos/ holds repositories only (its delimited listing is how they are found). Owners stay implicit: a profile creates nothing and is listed only while its owner has a repository.
  • Detail listings: ?detail=1 on /api/v1/owners and /api/v1/owners/{o}/repos returns objects with the metadata; the plain string[] shapes are unchanged.

GET needs read; every write needs admin — including ?description= on create, because Registry::create also answers Ok/201 for a repository already open on the instance (so a create does not prove newness; worth fixing separately). Fields are one line of plain text, bounded (512 / 100 chars, 8 KiB documents); writes strict, reads lenient (unknown keys ignored for rolling binaries). Metadata answers are SWR with a body-digest ETag.

Also fixes a gap: the SDK's browser lane addressed /api-browser/v1/owners*, which was never routed; all owners* routes now exist on both lanes.

UI: owners show their display name (id beside it), the owner page lists repository descriptions, the repo header shows the description. SDK: owners.listDetail(), owners.reposDetail(), owners.profile.*, repo.description.*, repo.create({description}).

Round trips (ROUNDTRIPS.md row added): git paths, pushes, syncs, plain listings unchanged. A description/profile GET = 1 object GET. ?detail=1 = the listing + one GET per item, 32 in flight — opt-in per call, and the UI's home page uses it. A conditional-GET cache would cut the steady state further; left out to keep "every read revalidates" untouched.

Tests: validation, lenient reads, serialization; api_v1::descriptions_and_owner_profiles (auth 401/403/204, both lanes, ETag → 304, 400/413 limits, unknown repo → 404, create-with-description, detail listings with absent fields, delete removes the description). pnpm run build passes.

Part of #94. Each of these PRs claims the next decision number in AGENTS.md (D50/D5x); renumber on merge as you prefer.

🤖 Generated with Claude Code

Opaque owner/repo ids (UUIDs from another system) leave the owner list and
repo lists unreadable: walgit has nowhere to keep the name a person reads.
Two small control objects, shaped like policy.json (D16) and for the same
reasons not on the WAL (D50):

- repos/<o>/<r>/description.json  {"description"}
  GET|PUT|DELETE /{o}/{r}/api[-browser]/description; create accepts
  ?description= (admin, validated before anything is created);
  `walgit repo describe <repo> [--set TEXT|--clear]`.
- owners/<o>/profile.json  {"display_name","description"}
  GET|PUT|DELETE /api[-browser]/v1/owners/{owner}. Bucket root, not under
  repos/: that prefix holds repositories only and its delimited listing is
  how the registry finds them. Owners stay implicit: a profile creates
  nothing and is listed only while its owner has a repository.
- ?detail=1 on /api/v1/owners and /api/v1/owners/{o}/repos returns objects
  with the metadata; the plain string[] shapes are unchanged.

Read for GET, admin for every write. One line of plain text, bounded
(description 512, display_name 100 chars, 8 KiB body/object); strict on
write (unknown keys, non-strings, control characters -> 400, 413 above the
bound), lenient on read (unknown keys ignored for rolling binaries; an
unparsable or oversized object reads as absent). Metadata answers are SWR
with a body-digest ETag.

Also: the owner listings were routed on /api/v1 only while the SDK's
browser lane addresses /api-browser/v1/owners*; all owners* routes are now
on both lanes.

UI: the home page shows display_name first with the id beside it, the owner
page shows the profile and each repository's description, the repo header
shows the description. SDK: owners.listDetail/reposDetail/profile.*,
repo.description.*, create({description}).

Round trips (docs/ROUNDTRIPS.md row added):
- git paths, push, every sync, plain listings: before = after (0 extra).
- GET description / owner profile: 1 object GET, the description's
  concurrent with the repository-exists check (depth 1 on a warm handle).
- ?detail=1: listing + one GET per listed owner resp. repository, 32 in
  flight, depth ceil(n/32) after the listing. Opt-in only.
- No CAS'd object is written; no manifest write; no in-process cache
  (principle IV; policy.json is read the same way).

Git's own `description` file is not written: the bare repository is a
disposable per-instance cache and nothing git serves reads it; keeping it
correct would need a hook on every materialization and every change.

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

Copy link
Copy Markdown
Author

Heads-up from our integration of the #94 PRs (proxy mode + metadata + default HEAD + baseline together, full workspace suite green): with this PR and #97 both merged, proxy owner scope did not cover the owner-profile routes. The scope check applies to routes with both {owner} and {repo}; /api[-browser]/v1/owners/{owner} has only {owner}, so a caller scoped to acme could read, overwrite or delete another owner's profile. Fix: the profile handlers answer the same 404 for out-of-scope owners, plus a test pinning every owner-only and repo route the two PRs add — conxai-technologies/walgit@9067576. Whichever of #96/#97 lands second should carry it; happy to add it to either branch.

🤖 Generated with Claude Code

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.

2 participants