2026-04-17 14:26:31 +02:00
{
"openapi" : "3.1.0" ,
"info" : {
"title" : "Omnigraph API" ,
"description" : "HTTP API for the Omnigraph graph database" ,
"license" : {
"name" : "MIT" ,
"identifier" : "MIT"
} ,
2026-05-10 14:02:28 +00:00
"version" : "0.4.2"
2026-04-17 14:26:31 +02:00
} ,
"paths" : {
"/branches" : {
"get" : {
"tags" : [
"branches"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "List all branches." ,
"description" : "Returns branch names sorted alphabetically. Read-only." ,
2026-04-17 14:26:31 +02:00
"operationId" : "listBranches" ,
"responses" : {
"200" : {
"description" : "List of branches" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/BranchListOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
} ,
"post" : {
"tags" : [
"branches"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Create a new branch." ,
"description" : "Forks `name` off of `from` (defaults to `main`). The new branch shares\ntable data with its parent until it is mutated. Returns 409 if `name`\nalready exists." ,
2026-04-17 14:26:31 +02:00
"operationId" : "createBranch" ,
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/BranchCreateRequest"
}
}
} ,
"required" : true
} ,
"responses" : {
"200" : {
"description" : "Branch created" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/BranchCreateOutput"
}
}
}
} ,
"400" : {
"description" : "Bad request" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"409" : {
"description" : "Branch already exists" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-05-08 17:49:02 +02:00
} ,
"429" : {
"description" : "Per-actor admission cap exceeded; honor `Retry-After` header" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-04-17 14:26:31 +02:00
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/branches/merge" : {
"post" : {
"tags" : [
"branches"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Merge one branch into another." ,
"description" : "Merges `source` into `target` (defaults to `main`). Outcome is one of\n`already_up_to_date`, `fast_forward`, or `merged`. Returns 409 with the\nlist of conflicts if the merge cannot be completed; the target is left\nunchanged in that case. **Destructive** to `target` on success." ,
2026-04-17 14:26:31 +02:00
"operationId" : "mergeBranches" ,
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/BranchMergeRequest"
}
}
} ,
"required" : true
} ,
"responses" : {
"200" : {
"description" : "Branches merged" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/BranchMergeOutput"
}
}
}
} ,
"400" : {
"description" : "Bad request" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"409" : {
"description" : "Merge conflict" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-05-08 17:49:02 +02:00
} ,
"429" : {
"description" : "Per-actor admission cap exceeded; honor `Retry-After` header" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-04-17 14:26:31 +02:00
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/branches/{branch}" : {
"delete" : {
"tags" : [
"branches"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Delete a branch." ,
"description" : "**Irreversible.** Removes the branch pointer; commits remain reachable\nonly if referenced by another branch. Returns 404 if the branch does not\nexist." ,
2026-04-17 14:26:31 +02:00
"operationId" : "deleteBranch" ,
"parameters" : [
{
"name" : "branch" ,
"in" : "path" ,
"description" : "Branch name to delete" ,
"required" : true ,
"schema" : {
"type" : "string"
}
}
] ,
"responses" : {
"200" : {
"description" : "Branch deleted" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/BranchDeleteOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"404" : {
"description" : "Branch not found" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-05-08 17:49:02 +02:00
} ,
"429" : {
"description" : "Per-actor admission cap exceeded; honor `Retry-After` header" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-04-17 14:26:31 +02:00
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/change" : {
"post" : {
"tags" : [
"mutations"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Apply a GQ mutation to a branch." ,
"description" : "Writes to the named `branch` (defaults to `main`). Mutations are atomic\nper call and produce a new commit. Returns counts of nodes and edges\naffected. **Destructive**: on success the branch is updated; rejected\nmutations may still acquire locks briefly. Returns 409 on merge conflict." ,
2026-04-17 14:26:31 +02:00
"operationId" : "change" ,
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ChangeRequest"
}
}
} ,
"required" : true
} ,
"responses" : {
"200" : {
"description" : "Mutation results" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ChangeOutput"
}
}
}
} ,
"400" : {
"description" : "Bad request" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"409" : {
"description" : "Merge conflict" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-05-08 17:49:02 +02:00
} ,
"429" : {
"description" : "Per-actor admission cap exceeded; honor `Retry-After` header" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-04-17 14:26:31 +02:00
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/commits" : {
"get" : {
"tags" : [
"commits"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "List commits." ,
"description" : "Filter by `branch` to get the commits on a single branch (most recent\nfirst); omit to list across all branches. Read-only." ,
2026-04-17 14:26:31 +02:00
"operationId" : "listCommits" ,
"parameters" : [
{
"name" : "branch" ,
"in" : "query" ,
"required" : false ,
"schema" : {
"type" : [
"string" ,
"null"
]
}
}
] ,
"responses" : {
"200" : {
"description" : "List of commits" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/CommitListOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/commits/{commit_id}" : {
"get" : {
"tags" : [
"commits"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Get a single commit." ,
"description" : "Returns the commit's manifest version, parent commit(s), and creation\nmetadata. Read-only." ,
2026-04-17 14:26:31 +02:00
"operationId" : "getCommit" ,
"parameters" : [
{
"name" : "commit_id" ,
"in" : "path" ,
"description" : "Commit identifier" ,
"required" : true ,
"schema" : {
"type" : "string"
}
}
] ,
"responses" : {
"200" : {
"description" : "Commit details" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/CommitOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"404" : {
"description" : "Commit not found" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/export" : {
"post" : {
"tags" : [
"queries"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Stream the contents of a branch as NDJSON." ,
"description" : "Emits one JSON object per line (`application/x-ndjson`). Filter with\n`type_names` (node/edge type names) and/or `table_keys`; both empty\nstreams the entire branch. Suitable for large exports — the response is\nstreamed, not buffered. Read-only." ,
2026-04-17 14:26:31 +02:00
"operationId" : "export" ,
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ExportRequest"
}
}
} ,
"required" : true
} ,
"responses" : {
"200" : {
"description" : "Exported data as NDJSON" ,
"content" : {
"application/x-ndjson" : { }
}
} ,
"400" : {
"description" : "Bad request" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/healthz" : {
"get" : {
"tags" : [
"health"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Liveness probe." ,
"description" : "Returns server status and version. Unauthenticated; safe to call from any\ncaller. Use this to confirm the server is reachable before invoking other\nendpoints." ,
2026-04-17 14:26:31 +02:00
"operationId" : "health" ,
"responses" : {
"200" : {
"description" : "Server is healthy" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HealthOutput"
}
}
}
}
}
}
} ,
"/ingest" : {
"post" : {
"tags" : [
"mutations"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Bulk-ingest NDJSON data into a branch." ,
"description" : "`data` is NDJSON with one record per line. `mode` controls behavior on\nexisting rows: `merge` upserts by id (default), `append` blindly inserts,\n`overwrite` replaces table contents. If `branch` does not exist it is\ncreated from `from` (defaults to `main`). **Destructive** when `mode` is\n`overwrite` or when ingest produces conflicting writes." ,
2026-04-17 14:26:31 +02:00
"operationId" : "ingest" ,
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/IngestRequest"
}
}
} ,
"required" : true
} ,
"responses" : {
"200" : {
"description" : "Ingest results" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/IngestOutput"
}
}
}
} ,
"400" : {
"description" : "Bad request" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-05-08 17:49:02 +02:00
} ,
"429" : {
"description" : "Per-actor admission cap exceeded; honor `Retry-After` header" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-04-17 14:26:31 +02:00
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/read" : {
"post" : {
"tags" : [
"queries"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Execute a GQ read query." ,
"description" : "Runs the query in `query_source` against either a branch or a frozen\nsnapshot (mutually exclusive). When `query_source` defines multiple named\nqueries, pick one with `query_name`. `params` is a JSON object whose keys\nmatch the parameters declared by the query. Returns rows as a JSON array\nplus a `columns` list. Read-only." ,
2026-04-17 14:26:31 +02:00
"operationId" : "read" ,
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ReadRequest"
}
}
} ,
"required" : true
} ,
"responses" : {
"200" : {
"description" : "Query results" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ReadOutput"
}
}
}
} ,
"400" : {
"description" : "Bad request" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
2026-04-18 20:24:39 +02:00
"/schema" : {
"get" : {
"tags" : [
"schema"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Read the current schema source." ,
"description" : "Returns the project's schema as a single string in `.pg` source form.\nUseful for clients that want to introspect available types and tables\nbefore constructing GQ queries. Read-only." ,
2026-04-18 20:24:39 +02:00
"operationId" : "getSchema" ,
"responses" : {
"200" : {
"description" : "Current schema source" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/SchemaOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
2026-04-17 14:26:31 +02:00
"/schema/apply" : {
"post" : {
"tags" : [
"mutations"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Apply a schema migration." ,
"description" : "Diffs `schema_source` against the current schema and applies the resulting\nmigration steps (add/drop type, add/drop column, etc.). **Destructive**:\nsome steps drop data. Returns the list of steps applied; if `applied` is\nfalse the diff was unsupported and no changes were made." ,
2026-04-17 14:26:31 +02:00
"operationId" : "applySchema" ,
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/SchemaApplyRequest"
}
}
} ,
"required" : true
} ,
"responses" : {
"200" : {
"description" : "Schema apply results" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/SchemaApplyOutput"
}
}
}
} ,
"400" : {
"description" : "Bad request" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-05-08 17:49:02 +02:00
} ,
"429" : {
"description" : "Per-actor admission cap exceeded; honor `Retry-After` header" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
2026-04-17 14:26:31 +02:00
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
} ,
"/snapshot" : {
"get" : {
"tags" : [
"snapshots"
] ,
2026-04-25 16:36:51 +02:00
"summary" : "Read the current snapshot of a branch." ,
"description" : "Returns the manifest version plus per-table metadata (path, version, row\ncount) for every table on the branch. Defaults to `main` when `branch` is\nomitted. Read-only." ,
2026-04-17 14:26:31 +02:00
"operationId" : "getSnapshot" ,
"parameters" : [
{
"name" : "branch" ,
"in" : "query" ,
"required" : false ,
"schema" : {
"type" : [
"string" ,
"null"
]
}
}
] ,
"responses" : {
"200" : {
"description" : "Database snapshot" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/SnapshotOutput"
}
}
}
} ,
"401" : {
"description" : "Unauthorized" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
} ,
"403" : {
"description" : "Forbidden" ,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ErrorOutput"
}
}
}
}
} ,
"security" : [
{
"bearer_token" : [ ]
}
]
}
}
} ,
"components" : {
"schemas" : {
"BranchCreateOutput" : {
"type" : "object" ,
"required" : [
"uri" ,
"from" ,
"name"
] ,
"properties" : {
"actor_id" : {
"type" : [
"string" ,
"null"
]
} ,
"from" : {
"type" : "string"
} ,
"name" : {
"type" : "string"
} ,
"uri" : {
"type" : "string"
}
}
} ,
"BranchCreateRequest" : {
"type" : "object" ,
"required" : [
"name"
] ,
"properties" : {
"from" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Parent branch to fork from. Defaults to `main`."
2026-04-17 14:26:31 +02:00
} ,
"name" : {
2026-04-25 16:36:51 +02:00
"type" : "string" ,
"description" : "Name of the new branch. Must not already exist."
2026-04-17 14:26:31 +02:00
}
}
} ,
"BranchDeleteOutput" : {
"type" : "object" ,
"required" : [
"uri" ,
"name"
] ,
"properties" : {
"actor_id" : {
"type" : [
"string" ,
"null"
]
} ,
"name" : {
"type" : "string"
} ,
"uri" : {
"type" : "string"
}
}
} ,
"BranchListOutput" : {
"type" : "object" ,
"required" : [
"branches"
] ,
"properties" : {
"branches" : {
"type" : "array" ,
"items" : {
"type" : "string"
}
}
}
} ,
"BranchMergeOutcome" : {
"type" : "string" ,
"enum" : [
"already_up_to_date" ,
"fast_forward" ,
"merged"
]
} ,
"BranchMergeOutput" : {
"type" : "object" ,
"required" : [
"source" ,
"target" ,
"outcome"
] ,
"properties" : {
"actor_id" : {
"type" : [
"string" ,
"null"
]
} ,
"outcome" : {
"$ref" : "#/components/schemas/BranchMergeOutcome"
} ,
"source" : {
"type" : "string"
} ,
"target" : {
"type" : "string"
}
}
} ,
"BranchMergeRequest" : {
"type" : "object" ,
"required" : [
"source"
] ,
"properties" : {
"source" : {
2026-04-25 16:36:51 +02:00
"type" : "string" ,
"description" : "Source branch whose commits will be merged."
2026-04-17 14:26:31 +02:00
} ,
"target" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Target branch that will receive the merge. Defaults to `main`."
2026-04-17 14:26:31 +02:00
}
}
} ,
"ChangeOutput" : {
"type" : "object" ,
"required" : [
"branch" ,
"query_name" ,
"affected_nodes" ,
"affected_edges"
] ,
"properties" : {
"actor_id" : {
"type" : [
"string" ,
"null"
]
} ,
"affected_edges" : {
"type" : "integer" ,
"minimum" : 0
} ,
"affected_nodes" : {
"type" : "integer" ,
"minimum" : 0
} ,
"branch" : {
"type" : "string"
} ,
"query_name" : {
"type" : "string"
}
}
} ,
"ChangeRequest" : {
"type" : "object" ,
"required" : [
"query_source"
] ,
"properties" : {
"branch" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Target branch. Defaults to `main`."
} ,
"params" : {
"description" : "JSON object whose keys match the mutation's declared parameters."
2026-04-17 14:26:31 +02:00
} ,
"query_name" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Name of the mutation to run when `query_source` declares multiple."
2026-04-17 14:26:31 +02:00
} ,
"query_source" : {
2026-04-25 16:36:51 +02:00
"type" : "string" ,
"description" : "GQ mutation source containing `insert`, `update`, or `delete` statements.\nMay declare multiple named mutations; pick one with `query_name`." ,
"example" : "query insert_person($name: String, $age: I32) {\n insert Person { name: $name, age: $age }\n}"
2026-04-17 14:26:31 +02:00
}
}
} ,
"CommitListOutput" : {
"type" : "object" ,
"required" : [
"commits"
] ,
"properties" : {
"commits" : {
"type" : "array" ,
"items" : {
"$ref" : "#/components/schemas/CommitOutput"
}
}
}
} ,
"CommitOutput" : {
"type" : "object" ,
"required" : [
"graph_commit_id" ,
"manifest_version" ,
"created_at"
] ,
"properties" : {
"actor_id" : {
"type" : [
"string" ,
"null"
]
} ,
"created_at" : {
"type" : "integer" ,
2026-04-25 16:36:51 +02:00
"format" : "int64" ,
"description" : "Commit creation time as Unix epoch microseconds." ,
"example" : 1714000000000000
2026-04-17 14:26:31 +02:00
} ,
"graph_commit_id" : {
"type" : "string"
} ,
"manifest_branch" : {
"type" : [
"string" ,
"null"
]
} ,
"manifest_version" : {
"type" : "integer" ,
"format" : "int64" ,
"minimum" : 0
} ,
"merged_parent_commit_id" : {
"type" : [
"string" ,
"null"
]
} ,
"parent_commit_id" : {
"type" : [
"string" ,
"null"
]
}
}
} ,
"ErrorCode" : {
"type" : "string" ,
"enum" : [
"unauthorized" ,
"forbidden" ,
"bad_request" ,
"not_found" ,
"conflict" ,
server: flip AppState to Arc<Omnigraph>, wire admission on /change (PR 2 Step F)
The substantive PR 2 change. Removes the global server `RwLock<Omnigraph>`
that has serialized every mutating request across all actors. Disjoint
`(table, branch)` writes from different actors now run concurrently,
guarded only by the engine's per-(table, branch) write queue (PR 1b)
and per-actor admission control (PR 2 Step E).
AppState changes:
- `db: Arc<RwLock<Omnigraph>>` -> `engine: Arc<Omnigraph>`
- New field: `workload: Arc<workload::WorkloadController>` initialized
from env (`OMNIGRAPH_PER_ACTOR_INFLIGHT_MAX=16`,
`OMNIGRAPH_PER_ACTOR_BYTES_MAX=4GiB`,
`OMNIGRAPH_GLOBAL_REWRITE_MAX=4`).
- `tokio::sync::RwLock` import dropped.
Handler updates (16 sites):
- All `Arc::clone(&state.db).read_owned().await` and `write_owned()`
calls replaced with `let db = &state.engine`. Engine APIs are now
`&self` (Step C) so this works directly.
- `/export` clones `Arc<Omnigraph>` once and moves into the spawned
task instead of acquiring a long-held read lock.
- `/change` handler additionally wires
`state.workload.try_admit(&actor_arc, est_bytes)`. Cedar runs FIRST
so denied requests don't consume admission slots; admission runs
SECOND before the engine call. `est_bytes` uses the request body
size as a coarse proxy.
API surface additions (`api::ErrorCode`):
- `TooManyRequests` -> HTTP 429 (per-actor cap exceeded; respect
`Retry-After`)
- `ServiceUnavailable` -> HTTP 503 (global rewrite pool exhausted)
`ApiError` constructors `too_many_requests` / `service_unavailable` and
`from_workload_reject` (maps `RejectReason` variants to HTTP status).
Other mutating handlers (`/ingest`, `/branches/*`, `/branches/merge`,
`/schema/apply`) currently flow through the Arc<Omnigraph> path
without admission gates; wiring those is mechanical and lands as a
follow-up. The /change hot path covers the bulk of MR-686's load
profile.
OpenAPI regenerated to include the new ErrorCode variants.
102 lib + 39 server tests + 5 workload tests pass. The regression
sentinel `change_conflict_returns_manifest_conflict_409` continues
to pass (revalidation perf opt + per-table queue + publisher CAS
preserve manifest_conflict semantics under concurrent writers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:08:26 +02:00
"too_many_requests" ,
2026-04-17 14:26:31 +02:00
"internal"
]
} ,
"ErrorOutput" : {
"type" : "object" ,
"required" : [
"error"
] ,
"properties" : {
"code" : {
"oneOf" : [
{
"type" : "null"
} ,
{
"$ref" : "#/components/schemas/ErrorCode"
}
]
} ,
"error" : {
"type" : "string"
} ,
2026-04-30 08:52:50 +02:00
"manifest_conflict" : {
"oneOf" : [
{
"type" : "null"
} ,
{
"$ref" : "#/components/schemas/ManifestConflictOutput" ,
"description" : "Set when the conflict is a publisher CAS rejection\n(`ManifestConflictDetails::ExpectedVersionMismatch`). The caller's\npre-write view of `table_key` was at version `expected` but the\nmanifest is now at `actual`. Refresh and retry."
}
]
} ,
2026-04-17 14:26:31 +02:00
"merge_conflicts" : {
"type" : "array" ,
"items" : {
"$ref" : "#/components/schemas/MergeConflictOutput"
}
}
}
} ,
"ExportRequest" : {
"type" : "object" ,
"properties" : {
"branch" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Branch to export. Defaults to `main`."
2026-04-17 14:26:31 +02:00
} ,
"table_keys" : {
"type" : "array" ,
"items" : {
"type" : "string"
2026-04-25 16:36:51 +02:00
} ,
"description" : "Restrict the export to these table keys. Empty exports all tables."
2026-04-17 14:26:31 +02:00
} ,
"type_names" : {
"type" : "array" ,
"items" : {
"type" : "string"
2026-04-25 16:36:51 +02:00
} ,
"description" : "Restrict the export to these node/edge type names. Empty exports all types."
2026-04-17 14:26:31 +02:00
}
}
} ,
"HealthOutput" : {
"type" : "object" ,
"required" : [
"status" ,
"version"
] ,
"properties" : {
"source_version" : {
"type" : [
"string" ,
"null"
]
} ,
"status" : {
"type" : "string"
} ,
"version" : {
"type" : "string"
}
}
} ,
"IngestOutput" : {
"type" : "object" ,
"required" : [
"uri" ,
"branch" ,
"base_branch" ,
"branch_created" ,
"mode" ,
"tables"
] ,
"properties" : {
"actor_id" : {
"type" : [
"string" ,
"null"
]
} ,
"base_branch" : {
"type" : "string"
} ,
"branch" : {
"type" : "string"
} ,
"branch_created" : {
"type" : "boolean"
} ,
"mode" : {
"$ref" : "#/components/schemas/LoadMode"
} ,
"tables" : {
"type" : "array" ,
"items" : {
"$ref" : "#/components/schemas/IngestTableOutput"
}
} ,
"uri" : {
"type" : "string"
}
}
} ,
"IngestRequest" : {
"type" : "object" ,
"required" : [
"data"
] ,
"properties" : {
"branch" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Target branch. Created from `from` if it does not yet exist. Defaults to `main`."
2026-04-17 14:26:31 +02:00
} ,
"data" : {
2026-04-25 16:36:51 +02:00
"type" : "string" ,
"description" : "NDJSON payload: one record per line, each shaped\n`{\"type\": \"<TypeName>\", \"data\": {...}}`." ,
"example" : "{\"type\": \"Person\", \"data\": {\"name\": \"Alice\", \"age\": 30}}\n{\"type\": \"Person\", \"data\": {\"name\": \"Bob\", \"age\": 25}}"
2026-04-17 14:26:31 +02:00
} ,
"from" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Parent branch used to create `branch` if it does not exist. Defaults to `main`."
2026-04-17 14:26:31 +02:00
} ,
"mode" : {
"oneOf" : [
{
"type" : "null"
} ,
{
2026-04-25 16:36:51 +02:00
"$ref" : "#/components/schemas/LoadMode" ,
"description" : "How existing rows are handled. Defaults to `merge`."
2026-04-17 14:26:31 +02:00
}
]
}
}
} ,
"IngestTableOutput" : {
"type" : "object" ,
"required" : [
"table_key" ,
"rows_loaded"
] ,
"properties" : {
"rows_loaded" : {
"type" : "integer" ,
"minimum" : 0
} ,
"table_key" : {
"type" : "string"
}
}
} ,
"LoadMode" : {
"type" : "string" ,
"description" : "Shadow enum for documenting [`LoadMode`] in the OpenAPI schema." ,
"enum" : [
"overwrite" ,
"append" ,
"merge"
]
} ,
2026-04-30 08:52:50 +02:00
"ManifestConflictOutput" : {
"type" : "object" ,
"description" : "Structured details for a publisher-level OCC failure. Surfaces alongside\nHTTP 409 when a write was rejected because the caller's pre-write view of\none table's manifest version was stale relative to the current head. The\nexpected/actual fields tell the client which table to refresh." ,
"required" : [
"table_key" ,
"expected" ,
"actual"
] ,
"properties" : {
"actual" : {
"type" : "integer" ,
"format" : "int64" ,
"minimum" : 0
} ,
"expected" : {
"type" : "integer" ,
"format" : "int64" ,
"minimum" : 0
} ,
"table_key" : {
"type" : "string"
}
}
} ,
2026-04-17 14:26:31 +02:00
"MergeConflictKindOutput" : {
"type" : "string" ,
"enum" : [
"divergent_insert" ,
"divergent_update" ,
"delete_vs_update" ,
"orphan_edge" ,
"unique_violation" ,
"cardinality_violation" ,
"value_constraint_violation"
]
} ,
"MergeConflictOutput" : {
"type" : "object" ,
"required" : [
"table_key" ,
"kind" ,
"message"
] ,
"properties" : {
"kind" : {
"$ref" : "#/components/schemas/MergeConflictKindOutput"
} ,
"message" : {
"type" : "string"
} ,
"row_id" : {
"type" : [
"string" ,
"null"
]
} ,
"table_key" : {
"type" : "string"
}
}
} ,
"ReadOutput" : {
"type" : "object" ,
"required" : [
"query_name" ,
"target" ,
"row_count" ,
"rows"
] ,
"properties" : {
"columns" : {
"type" : "array" ,
"items" : {
"type" : "string"
}
} ,
"query_name" : {
"type" : "string"
} ,
"row_count" : {
"type" : "integer" ,
"minimum" : 0
} ,
"rows" : { } ,
"target" : {
"$ref" : "#/components/schemas/ReadTargetOutput"
}
}
} ,
"ReadRequest" : {
"type" : "object" ,
"required" : [
"query_source"
] ,
"properties" : {
"branch" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Branch to read from. Mutually exclusive with `snapshot`. Defaults to `main`."
} ,
"params" : {
"description" : "JSON object whose keys match the query's declared parameters."
2026-04-17 14:26:31 +02:00
} ,
"query_name" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Name of the query to run when `query_source` declares multiple. Optional\nwhen only one query is declared."
2026-04-17 14:26:31 +02:00
} ,
"query_source" : {
2026-04-25 16:36:51 +02:00
"type" : "string" ,
"description" : "GQ query source. May declare one or more named queries; pick one with\n`query_name` if there is more than one." ,
"example" : "query get_person($name: String) {\n match {\n $p: Person { name: $name }\n }\n return { $p.name, $p.age }\n}"
2026-04-17 14:26:31 +02:00
} ,
"snapshot" : {
"type" : [
"string" ,
"null"
2026-04-25 16:36:51 +02:00
] ,
"description" : "Snapshot id to read from. Mutually exclusive with `branch`."
2026-04-17 14:26:31 +02:00
}
}
} ,
"ReadTargetOutput" : {
"type" : "object" ,
"properties" : {
"branch" : {
"type" : [
"string" ,
"null"
]
} ,
"snapshot" : {
"type" : [
"string" ,
"null"
]
}
}
} ,
"SchemaApplyOutput" : {
"type" : "object" ,
"required" : [
"uri" ,
"supported" ,
"applied" ,
"step_count" ,
"manifest_version" ,
"steps"
] ,
"properties" : {
"applied" : {
"type" : "boolean"
} ,
"manifest_version" : {
"type" : "integer" ,
"format" : "int64" ,
"minimum" : 0
} ,
"step_count" : {
"type" : "integer" ,
"minimum" : 0
} ,
"steps" : {
"type" : "array" ,
"items" : { }
} ,
"supported" : {
"type" : "boolean"
} ,
"uri" : {
"type" : "string"
}
}
} ,
"SchemaApplyRequest" : {
"type" : "object" ,
"required" : [
"schema_source"
] ,
"properties" : {
"schema_source" : {
2026-04-25 16:36:51 +02:00
"type" : "string" ,
"description" : "Project schema in `.pg` source form. The diff against the current\nschema produces the migration steps that will be applied." ,
"example" : "node Person {\n name: String @key\n age: I32?\n}\n\nedge Knows: Person -> Person"
2026-04-17 14:26:31 +02:00
}
}
} ,
2026-04-18 20:24:39 +02:00
"SchemaOutput" : {
"type" : "object" ,
"required" : [
"schema_source"
] ,
"properties" : {
"schema_source" : {
"type" : "string"
}
}
} ,
2026-04-17 14:26:31 +02:00
"SnapshotOutput" : {
"type" : "object" ,
"required" : [
"branch" ,
"manifest_version" ,
"tables"
] ,
"properties" : {
"branch" : {
"type" : "string"
} ,
"manifest_version" : {
"type" : "integer" ,
"format" : "int64" ,
"minimum" : 0
} ,
"tables" : {
"type" : "array" ,
"items" : {
"$ref" : "#/components/schemas/SnapshotTableOutput"
}
}
}
} ,
"SnapshotTableOutput" : {
"type" : "object" ,
"required" : [
"table_key" ,
"table_path" ,
"table_version" ,
"row_count"
] ,
"properties" : {
"row_count" : {
"type" : "integer" ,
"format" : "int64" ,
"minimum" : 0
} ,
"table_branch" : {
"type" : [
"string" ,
"null"
]
} ,
"table_key" : {
"type" : "string"
} ,
"table_path" : {
"type" : "string"
} ,
"table_version" : {
"type" : "integer" ,
"format" : "int64" ,
"minimum" : 0
}
}
}
} ,
"securitySchemes" : {
"bearer_token" : {
"type" : "http" ,
"scheme" : "bearer"
}
}
}
}