Skip to content

feat(graph): add repository-scoped graph publication - #123

Merged
balcsida merged 16 commits into
mainfrom
feat/codegraph/s1-08-publish-policy
Sep 27, 2026
Merged

balcsida merged 16 commits into
mainfrom
feat/codegraph/s1-08-publish-policy

Conversation

@balcsida

Copy link
Copy Markdown
Owner

Summary

Implements S1.08 of the CodeGraph parity roadmap: repository-scoped graph publication. Stage 2 (the CLI publishing a local CodeGraph index) depends on it. Until now, v2 artifacts could only be stored by tests; the public upload accepted v1 from administrators only.

  • v2 uploads. POST /v1/graph/uploads with application/vnd.graphnest.graph.v2+protobuf publishes a v2 generation for the indexed commit and answers 200 with the new generation, the one it replaced, and the verified content hash.
  • Separate permission. Administrators need no grant. Anyone else needs read access within their API token ceiling and a publication grant. Read access never implies publication.
  • Grants. Stored in graph_publication_grants (migration 037) and managed by administrators through PUT /v1/graph/publication-grants. The route reuses the supply-chain upload-grant handler, which this PR extracts so both routes share it.
  • Replacement safety. Every v2 upload names expected_generation, and replacing another producer (for example the managed scanner) also needs replace_producer=true. PostgreSQL compares the generation and indexed commit under the repository row lock, so concurrent, stale or advancing-SHA uploads get 409 and never overwrite the wrong generation.
  • Retries. An identical retry of the active v2 content returns deduplicated: true instead of a conflict. It matches on the verified semantic hash, which covers repository, commit and producer. Retired content cannot be reactivated this way.
  • Rechecks. The API token (through the request's fresh-principal hook), the grant and the indexed commit are checked before the body is read and again after parsing.
  • Preflight. Graph status gains a publication block: accepted versions, upload limit, whether the caller may publish, and the active generation. Capabilities now list upload versions [1, 2].
  • Audit. Each graph_uploads row records the publisher (api_token:<user id>), producer, commit, content hash, and activation and retirement.

v1 uploads are unchanged and remain administrator-only. Only the text of their 403 message changed.

Evidence

test/integration/graph_publication_test.go runs every S1.08 acceptance case against real PostgreSQL and real API tokens:

  • a grantee succeeds, and an administrator can publish without a grant;
  • read-only, wrong-repository, token-ceiling, expired-token and malformed uploads fail;
  • revoking the token or the grant while the body is uploading fails the request and leaves the active generation unchanged;
  • a retry deduplicates; stale and concurrent replacement conflict; an advancing SHA is rejected; a producer takeover needs replace_producer=true.

Temporarily removing the post-parse recheck made both this test and the service unit tests fail.

Compatibility, schema and security

  • Schema: migration 037 adds one empty table that cascades from repositories. It is additive and needs no configuration.
  • Security: a new publication path for non-administrators, gated by an explicit administrator grant. The grant never widens read access. MCP OAuth tokens remain confined to /mcp and cannot publish.
  • Behaviour change: republishing identical v2 content into storage now returns the active generation instead of creating a new generation or reporting a stale precondition. Four storage tests relied on identical republishes and now vary their content.
  • Known window: authority is final at the post-parse recheck. A grant revoked during the storage copy still lands; this is marked ponytail: in graphingest.Service.Publish.
  • Not declared: REST-published generations record no producer capability list. Stage 2 should declare one.

Verification

GOWORK=off go vet ./... && GOWORK=off go vet -tags=integration ./...
make test-race
make staticcheck
make postgres-test   # run with -race: postgres, authz, webhook, integration, indexer, server
docker run --rm -v "$PWD":/src -w /src ruby:3.4-alpine ruby scripts/check_openapi.rb

Local notes:

  • make openapi-check needs Ruby 2.7 or later; macOS system Ruby 2.6 cannot run it, so I ran it in a container.
  • A second local PostgreSQL run failed only the ten internal/postgres supply-chain job-claim tests, and main fails them the same way. The Docker VM clock was about 16 ms behind the host. Those tests enqueue at host time.Now() and then immediately claim against the database's now(), so the job is not yet due. Every graph and publication test passed in both runs.

Not in this PR

The CLI (Stage 2), MCP exposure of publication, and the rest of S1.06, S1.07, S1.09 and S1.10. The full Stage 1 gate has not been run, and this PR does not claim it passes.

🤖 Generated with Claude Code

balcsida added a commit that referenced this pull request Sep 27, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
balcsida and others added 16 commits September 27, 2026 16:02
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@balcsida
balcsida force-pushed the feat/codegraph/s1-08-publish-policy branch from 0d34355 to f61c105 Compare September 27, 2026 14:02
@balcsida
balcsida merged commit 8f51f3d into main Sep 27, 2026
11 of 12 checks passed
@balcsida
balcsida deleted the feat/codegraph/s1-08-publish-policy branch September 27, 2026 17:25
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