diff --git a/openapi.json b/openapi.json index e68cee4..011b565 100644 --- a/openapi.json +++ b/openapi.json @@ -1,3610 +1 @@ -{ - "components": { - "responses": { - "Forbidden": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "properties": { - "code": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - } - } - }, - "description": "Credentials are valid but lack the scope required for this route, or the principal lacks membership in the target project." - }, - "NotFound": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "properties": { - "code": { - "example": "not_found", - "type": "string" - }, - "message": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - } - } - }, - "description": "Resource not found OR not accessible to this principal. Leak parity: the response is identical in both cases so an attacker cannot enumerate resources via 404-vs-403 timing." - }, - "ProjectCreateConflict": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "properties": { - "code": { - "enum": [ - "name_taken", - "project_limit_reached" - ], - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - } - }, - "type": "object" - } - } - }, - "description": "The project could not be created because it conflicts with existing state. ``name_taken`` \u2014 an active project of yours already uses this name (case-insensitive); pick another. ``project_limit_reached`` \u2014 you are at the per-user project cap, which counts ARCHIVED projects too; delete a project to free a slot (archiving does not)." - }, - "ProjectCreateUnprocessable": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "properties": { - "code": { - "enum": [ - "invalid_name", - "unsupported_language", - "unsupported_intent" - ], - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - } - }, - "type": "object" - } - } - }, - "description": "A field in the request body is not acceptable. ``invalid_name`` \u2014 the name is blank after trimming. ``unsupported_language`` / ``unsupported_intent`` \u2014 the value is outside the server's allowlist. NOTE: this envelope is ``{detail: {code, message}}``, NOT FastAPI's ``HTTPValidationError``." - }, - "ProjectPatchConflict": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "properties": { - "code": { - "enum": [ - "name_taken" - ], - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - } - }, - "type": "object" - } - } - }, - "description": "The patch could not be applied because it conflicts with an existing active project. `name_taken` \u2014 reachable two ways: renaming to a name an active project of yours already has (case-insensitive), or restoring (`archived: false`) a project whose name was taken by something else while it was archived. Both need the name changed before the request can succeed." - }, - "ProjectPatchUnprocessable": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "properties": { - "code": { - "enum": [ - "no_fields", - "invalid_name" - ], - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - } - }, - "type": "object" - } - } - }, - "description": "A field in the request body is not acceptable. `no_fields` \u2014 neither `name` nor `archived` was supplied; at least one is required. `invalid_name` \u2014 `name` is present but blank after trimming." - }, - "RateLimited": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "example": "rate_limited", - "type": "string" - } - }, - "type": "object" - } - } - }, - "description": "Per-bucket rate limit exceeded. The response carries ``Retry-After`` and the standard ``X-RateLimit-*`` headers (Limit / Remaining / Reset).", - "headers": { - "Retry-After": { - "schema": { - "type": "integer" - } - }, - "X-RateLimit-Limit": { - "schema": { - "type": "integer" - } - }, - "X-RateLimit-Remaining": { - "schema": { - "type": "integer" - } - }, - "X-RateLimit-Reset": { - "schema": { - "type": "integer" - } - } - } - }, - "Unauthenticated": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "example": "unauthenticated", - "type": "string" - } - }, - "required": [ - "detail" - ], - "type": "object" - } - } - }, - "description": "No credentials, malformed credentials, or revoked credentials. The envelope is leak-parity (same shape across all 401 paths) so an attacker cannot distinguish revoked vs. unknown via the body." - }, - "Unprocessable": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "oneOf": [ - { - "type": "string" - }, - { - "properties": { - "delta_ordinal": { - "type": "integer" - }, - "delta_type": { - "type": [ - "string", - "null" - ] - }, - "error": { - "type": "string" - }, - "field": { - "type": [ - "string", - "null" - ] - }, - "reason": { - "type": [ - "string", - "null" - ] - }, - "recoverable": { - "type": "boolean" - }, - "recovery_hint": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - } - ] - } - }, - "type": "object" - } - } - }, - "description": "Request body failed validation. For delta routes the response carries ``error``, ``delta_ordinal``, ``delta_type``, ``field``, ``reason``, ``recoverable``, and ``recovery_hint`` so a client can auto-recover or surface a typed error." - }, - "VersionConflict": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "properties": { - "current_version": { - "type": "integer" - }, - "error": { - "example": "version_conflict", - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - } - } - }, - "description": "Optimistic concurrency check failed \u2014 the branch's current version is not the one the client sent in ``expected_version``. The response carries ``current_version`` so the client can refetch + retry." - } - }, - "schemas": { - "AddEdgeDelta": { - "description": "Insert an edge.", - "properties": { - "edge": { - "$ref": "#/components/schemas/Edge" - }, - "type": { - "const": "add_edge", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type", - "edge" - ], - "title": "AddEdgeDelta", - "type": "object" - }, - "AddNodeDelta": { - "description": "Insert a node.", - "properties": { - "node": { - "$ref": "#/components/schemas/Node" - }, - "type": { - "const": "add_node", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type", - "node" - ], - "title": "AddNodeDelta", - "type": "object" - }, - "BoundaryResponse": { - "additionalProperties": false, - "properties": { - "boundary": { - "$ref": "#/components/schemas/WireNode" - }, - "branch": { - "$ref": "#/components/schemas/BranchRef" - }, - "children": { - "items": { - "$ref": "#/components/schemas/WireNode" - }, - "title": "Children", - "type": "array" - }, - "edges": { - "items": { - "$ref": "#/components/schemas/WireEdge" - }, - "title": "Edges", - "type": "array" - }, - "paths": { - "additionalProperties": { - "type": "string" - }, - "description": "Dotted path for every node returned that has one, keyed by node id. Server-rendered because a scoped read returns a SLICE: the ancestors a path is built from are not in the payload, so a client cannot derive it. Every returned node appears in exactly one of `paths` or `unaddressable` \u2014 index this map for anything not listed there.", - "propertyNames": { - "format": "uuid" - }, - "title": "Paths", - "type": "object" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "unaddressable": { - "additionalProperties": { - "type": "string" - }, - "description": "Nodes that could not be given a dotted path, keyed by node id, with the reason: `empty_name` (the node, or an ancestor, has no name \u2014 legal while designing), `reserved_separator` (a name contains `.`), or `ambiguous` (two returned nodes would render the same path, so neither is safe to act on). These are data conditions a user fixes by renaming, not errors.", - "propertyNames": { - "format": "uuid" - }, - "title": "Unaddressable", - "type": "object" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branch", - "boundary", - "children", - "edges", - "paths", - "unaddressable" - ], - "title": "BoundaryResponse", - "type": "object" - }, - "BranchCreateResponse": { - "additionalProperties": false, - "properties": { - "branch": { - "$ref": "#/components/schemas/BranchMeta" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branch" - ], - "title": "BranchCreateResponse", - "type": "object" - }, - "BranchListResponse": { - "additionalProperties": false, - "properties": { - "branches": { - "items": { - "$ref": "#/components/schemas/BranchMeta" - }, - "title": "Branches", - "type": "array" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branches" - ], - "title": "BranchListResponse", - "type": "object" - }, - "BranchMeta": { - "additionalProperties": false, - "properties": { - "base_main_version": { - "title": "Base Main Version", - "type": "integer" - }, - "created_at": { - "title": "Created At", - "type": "string" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "is_main": { - "title": "Is Main", - "type": "boolean" - }, - "last_active_at": { - "title": "Last Active At", - "type": "string" - }, - "merged_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Merged At" - }, - "name": { - "title": "Name", - "type": "string" - }, - "owner_id": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Owner Id" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "status": { - "title": "Status", - "type": "string" - }, - "version": { - "title": "Version", - "type": "integer" - } - }, - "required": [ - "id", - "project_id", - "owner_id", - "name", - "is_main", - "version", - "base_main_version", - "status", - "last_active_at", - "created_at", - "merged_at" - ], - "title": "BranchMeta", - "type": "object" - }, - "BranchNotActiveBody": { - "additionalProperties": false, - "properties": { - "current_status": { - "title": "Current Status", - "type": "string" - }, - "error": { - "const": "branch_not_active", - "title": "Error", - "type": "string" - } - }, - "required": [ - "error", - "current_status" - ], - "title": "BranchNotActiveBody", - "type": "object" - }, - "BranchRef": { - "additionalProperties": false, - "properties": { - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "version": { - "title": "Version", - "type": "integer" - } - }, - "required": [ - "id", - "version" - ], - "title": "BranchRef", - "type": "object" - }, - "DeleteEdgeDelta": { - "description": "Remove an edge.", - "properties": { - "edge_id": { - "format": "uuid", - "title": "Edge Id", - "type": "string" - }, - "type": { - "const": "delete_edge", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type", - "edge_id" - ], - "title": "DeleteEdgeDelta", - "type": "object" - }, - "DeleteNodeDelta": { - "description": "Cascade-delete a node and its descendant subtree.", - "properties": { - "node_id": { - "format": "uuid", - "title": "Node Id", - "type": "string" - }, - "type": { - "const": "delete_node", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type", - "node_id" - ], - "title": "DeleteNodeDelta", - "type": "object" - }, - "DeltaApplyResponse": { - "additionalProperties": false, - "properties": { - "applied": { - "title": "Applied", - "type": "boolean" - }, - "branch": { - "$ref": "#/components/schemas/BranchRef" - }, - "delta_count": { - "title": "Delta Count", - "type": "integer" - }, - "positions_applied": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Positions Applied" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branch", - "applied", - "delta_count" - ], - "title": "DeltaApplyResponse", - "type": "object" - }, - "DeltaFieldErrorBody": { - "additionalProperties": false, - "description": "The 7-key per-delta envelope (always all keys present) the frontend\nauto-recovery consumes. Shared by every 422 delta error; `error`\ndiscriminates the cause.", - "properties": { - "delta_ordinal": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Delta Ordinal" - }, - "delta_type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Delta Type" - }, - "error": { - "enum": [ - "malformed_delta_field", - "unknown_delta_type", - "delta_not_object", - "delta_too_large", - "batch_too_large", - "batch_count_exceeded" - ], - "title": "Error", - "type": "string" - }, - "field": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Field" - }, - "reason": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Reason" - }, - "recoverable": { - "title": "Recoverable", - "type": "boolean" - }, - "recovery_hint": { - "anyOf": [ - { - "$ref": "#/components/schemas/RecoveryHint" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "error", - "delta_ordinal", - "delta_type", - "field", - "reason", - "recoverable", - "recovery_hint" - ], - "title": "DeltaFieldErrorBody", - "type": "object" - }, - "Edge": { - "description": "An edge connecting two nodes' ports.\n\nEndpoints are given as `source_handle` / `target_handle`, each a port\nid. Unknown keys are ignored rather than rejected.", - "properties": { - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "source_handle": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Source Handle" - }, - "target_handle": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Target Handle" - } - }, - "required": [ - "id" - ], - "title": "Edge", - "type": "object" - }, - "Finding": { - "additionalProperties": false, - "description": "A single coherence finding over the graph that results from a delta\nbatch. All three v1 ``code``s are ``error``-severity; ``severity`` keeps the\n``warning`` arm in the contract for future advisory codes. ``locator`` is the\nnode / port / edge id (a string) the finding is about.", - "properties": { - "code": { - "enum": [ - "unsatisfied_input", - "dangling_wire", - "type_mismatch" - ], - "title": "Code", - "type": "string" - }, - "locator": { - "title": "Locator", - "type": "string" - }, - "message": { - "title": "Message", - "type": "string" - }, - "severity": { - "enum": [ - "error", - "warning" - ], - "title": "Severity", - "type": "string" - } - }, - "required": [ - "code", - "severity", - "locator", - "message" - ], - "title": "Finding", - "type": "object" - }, - "FlattenBoundaryDelta": { - "description": "Flatten a boundary node: delete it and promote its children to its parent.", - "properties": { - "node_id": { - "format": "uuid", - "title": "Node Id", - "type": "string" - }, - "type": { - "const": "flatten_boundary", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type", - "node_id" - ], - "title": "FlattenBoundaryDelta", - "type": "object" - }, - "GraphResponse": { - "additionalProperties": false, - "properties": { - "branch": { - "$ref": "#/components/schemas/BranchRef" - }, - "edges": { - "items": { - "$ref": "#/components/schemas/WireEdge" - }, - "title": "Edges", - "type": "array" - }, - "nodes": { - "items": { - "$ref": "#/components/schemas/WireNode" - }, - "title": "Nodes", - "type": "array" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branch", - "nodes", - "edges" - ], - "title": "GraphResponse", - "type": "object" - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "title": "Detail", - "type": "array" - } - }, - "title": "HTTPValidationError", - "type": "object" - }, - "HealthzResponse": { - "additionalProperties": false, - "properties": { - "ok": { - "title": "Ok", - "type": "boolean" - } - }, - "required": [ - "ok" - ], - "title": "HealthzResponse", - "type": "object" - }, - "MainBranchRef": { - "additionalProperties": false, - "description": "The seeded main branch as returned by project-create.\n\nDeliberately narrower than ``BranchMeta``: create returns only what the\nclient can't mint locally and needs for its next call. Pinning the real\nshape (not ``BranchMeta``) keeps the generated SDK honest.", - "properties": { - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "version": { - "title": "Version", - "type": "integer" - } - }, - "required": [ - "id", - "version" - ], - "title": "MainBranchRef", - "type": "object" - }, - "Node": { - "description": "A behavior or boundary node.", - "properties": { - "data": { - "$ref": "#/components/schemas/NodeData" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "kind": { - "enum": [ - "behavior", - "boundary", - "state", - "io", - "interface" - ], - "title": "Kind", - "type": "string" - }, - "parent_id": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Parent Id" - } - }, - "required": [ - "id", - "kind" - ], - "title": "Node", - "type": "object" - }, - "NodeData": { - "description": "A node's data payload; its shape varies by kind and whether the node is\nexternal.\n\nOn update, key-presence semantics apply: a field present with a null value is\nset to null, while an omitted field is left unchanged. All fields are optional\nat the type level \u2014 cross-field rules (e.g. behavior nodes reject `user_kind`;\nexternal nodes require `external_kind`) are enforced server-side, so a partial\nupdate is not blocked by \"missing required field\" errors that don't apply to it.", - "properties": { - "aliases": { - "items": { - "maxLength": 500, - "type": "string" - }, - "maxItems": 50, - "title": "Aliases", - "type": "array" - }, - "config": { - "items": { - "$ref": "#/components/schemas/Port" - }, - "title": "Config", - "type": "array" - }, - "constraints": { - "items": { - "type": "string" - }, - "title": "Constraints", - "type": "array" - }, - "description": { - "default": "", - "title": "Description", - "type": "string" - }, - "documentation_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Documentation Url" - }, - "external_kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "External Kind" - }, - "inputs": { - "items": { - "$ref": "#/components/schemas/Port" - }, - "title": "Inputs", - "type": "array" - }, - "is_external": { - "default": false, - "title": "Is External", - "type": "boolean" - }, - "is_test_node": { - "default": false, - "title": "Is Test Node", - "type": "boolean" - }, - "kind": { - "anyOf": [ - { - "enum": [ - "behavior", - "boundary", - "state", - "io", - "interface" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Kind" - }, - "language": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Language" - }, - "name": { - "default": "", - "title": "Name", - "type": "string" - }, - "outputs": { - "items": { - "$ref": "#/components/schemas/Port" - }, - "title": "Outputs", - "type": "array" - }, - "parent_id": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Parent Id" - }, - "path_prefix": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Path Prefix" - }, - "protocol": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Protocol" - }, - "source_decisions": { - "items": { - "format": "uuid", - "type": "string" - }, - "title": "Source Decisions", - "type": "array" - }, - "status": { - "default": "idle", - "title": "Status", - "type": "string" - }, - "user_kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "User Kind" - }, - "verifications": { - "items": { - "$ref": "#/components/schemas/Verification" - }, - "title": "Verifications", - "type": "array" - } - }, - "title": "NodeData", - "type": "object" - }, - "NodeNeighborhoodResponse": { - "additionalProperties": false, - "properties": { - "branch": { - "$ref": "#/components/schemas/BranchRef" - }, - "edges": { - "items": { - "$ref": "#/components/schemas/WireEdge" - }, - "title": "Edges", - "type": "array" - }, - "neighbors": { - "items": { - "$ref": "#/components/schemas/WireNode" - }, - "title": "Neighbors", - "type": "array" - }, - "node": { - "$ref": "#/components/schemas/WireNode" - }, - "paths": { - "additionalProperties": { - "type": "string" - }, - "description": "Dotted path for every node returned that has one, keyed by node id. Server-rendered because a scoped read returns a SLICE: the ancestors a path is built from are not in the payload, so a client cannot derive it. Every returned node appears in exactly one of `paths` or `unaddressable` \u2014 index this map for anything not listed there.", - "propertyNames": { - "format": "uuid" - }, - "title": "Paths", - "type": "object" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "unaddressable": { - "additionalProperties": { - "type": "string" - }, - "description": "Nodes that could not be given a dotted path, keyed by node id, with the reason: `empty_name` (the node, or an ancestor, has no name \u2014 legal while designing), `reserved_separator` (a name contains `.`), or `ambiguous` (two returned nodes would render the same path, so neither is safe to act on). These are data conditions a user fixes by renaming, not errors.", - "propertyNames": { - "format": "uuid" - }, - "title": "Unaddressable", - "type": "object" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branch", - "node", - "neighbors", - "edges", - "paths", - "unaddressable" - ], - "title": "NodeNeighborhoodResponse", - "type": "object" - }, - "NotFoundBody": { - "additionalProperties": false, - "description": "Returned when a delta targets a node that doesn't exist (404). `node_id`\nidentifies the missing target.", - "properties": { - "error": { - "const": "not_found", - "title": "Error", - "type": "string" - }, - "node_id": { - "title": "Node Id", - "type": "string" - }, - "reason": { - "title": "Reason", - "type": "string" - } - }, - "required": [ - "error", - "node_id", - "reason" - ], - "title": "NotFoundBody", - "type": "object" - }, - "Port": { - "description": "An input, output, or config port on a node.", - "properties": { - "contract_name": { - "anyOf": [ - { - "maxLength": 500, - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Contract Name" - }, - "description": { - "default": "", - "title": "Description", - "type": "string" - }, - "external": { - "default": false, - "title": "External", - "type": "boolean" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "name": { - "default": "", - "title": "Name", - "type": "string" - }, - "type": { - "default": "", - "title": "Type", - "type": "string" - } - }, - "required": [ - "id" - ], - "title": "Port", - "type": "object" - }, - "Position": { - "additionalProperties": false, - "properties": { - "x": { - "title": "X", - "type": "number" - }, - "y": { - "title": "Y", - "type": "number" - } - }, - "required": [ - "x", - "y" - ], - "title": "Position", - "type": "object" - }, - "ProjectCreateResponse": { - "additionalProperties": false, - "properties": { - "main_branch": { - "$ref": "#/components/schemas/MainBranchRef" - }, - "project": { - "$ref": "#/components/schemas/ProjectOut" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project", - "main_branch" - ], - "title": "ProjectCreateResponse", - "type": "object" - }, - "ProjectOut": { - "additionalProperties": false, - "properties": { - "archived": { - "title": "Archived", - "type": "boolean" - }, - "created_at": { - "title": "Created At", - "type": "string" - }, - "h2o_schema_version": { - "title": "H2O Schema Version", - "type": "integer" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "intent": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Intent" - }, - "language": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Language" - }, - "last_opened_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Last Opened At" - }, - "name": { - "title": "Name", - "type": "string" - }, - "updated_at": { - "title": "Updated At", - "type": "string" - } - }, - "required": [ - "id", - "name", - "language", - "intent", - "archived", - "h2o_schema_version", - "created_at", - "updated_at", - "last_opened_at" - ], - "title": "ProjectOut", - "type": "object" - }, - "ProjectPatchResponse": { - "additionalProperties": false, - "description": "Rename/archive returns the project alone.\n\nNo ``main_branch``, unlike create: the branch is not affected by either\nedit, and echoing it would imply otherwise.", - "properties": { - "project": { - "$ref": "#/components/schemas/ProjectOut" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project" - ], - "title": "ProjectPatchResponse", - "type": "object" - }, - "ProjectsListResponse": { - "additionalProperties": false, - "properties": { - "projects": { - "items": { - "$ref": "#/components/schemas/ProjectOut" - }, - "title": "Projects", - "type": "array" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "projects" - ], - "title": "ProjectsListResponse", - "type": "object" - }, - "RecoveryHint": { - "description": "The finite set of recovery hints a client may receive in\n`ApiError.recovery_hint`:\n\n - \"regenerate_uuid\" \u2014 mint a new UUID at `field` and retry\n - \"drop_field\" \u2014 delete `field` and retry\n - null \u2014 not auto-recoverable; user intervention needed\n\nA closed set, so integrations can switch over the value; a new hint is added\nhere deliberately, giving clients a chance to handle it.", - "enum": [ - "regenerate_uuid", - "drop_field" - ], - "title": "RecoveryHint", - "type": "string" - }, - "ReparentNodeDelta": { - "description": "Move a node to a new parent (or to top-level).\n\n`parent_id` must be present on the wire (use null for top-level); an absent key\nis rejected, so a client bug surfaces as a clean parse error rather than an\nambiguous no-op.", - "properties": { - "node_id": { - "format": "uuid", - "title": "Node Id", - "type": "string" - }, - "parent_id": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Parent Id" - }, - "type": { - "const": "reparent_node", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type", - "node_id", - "parent_id" - ], - "title": "ReparentNodeDelta", - "type": "object" - }, - "SubtreeResponse": { - "additionalProperties": false, - "description": "A node plus its descendants to a bounded depth.\n\n``nodes`` EXCLUDES ``root``. ``truncated`` is true when the walk stopped\nwith descendants still below it \u2014 a caller that cannot distinguish a\ncomplete cell from a slice will treat a slice as complete, so the signal\nis explicit rather than inferred from ``depth``.\n\n``cross_boundary_edges`` carries edges with exactly one endpoint inside\nthe returned set: real dependencies of this slice, which a scoped read\nwould otherwise hide.", - "properties": { - "branch": { - "$ref": "#/components/schemas/BranchRef" - }, - "cross_boundary_edges": { - "items": { - "$ref": "#/components/schemas/WireEdge" - }, - "title": "Cross Boundary Edges", - "type": "array" - }, - "depth": { - "title": "Depth", - "type": "integer" - }, - "edges": { - "items": { - "$ref": "#/components/schemas/WireEdge" - }, - "title": "Edges", - "type": "array" - }, - "nodes": { - "items": { - "$ref": "#/components/schemas/WireNode" - }, - "title": "Nodes", - "type": "array" - }, - "paths": { - "additionalProperties": { - "type": "string" - }, - "description": "Dotted path for every node returned that has one, keyed by node id. Server-rendered because a scoped read returns a SLICE: the ancestors a path is built from are not in the payload, so a client cannot derive it. Every returned node appears in exactly one of `paths` or `unaddressable` \u2014 index this map for anything not listed there.", - "propertyNames": { - "format": "uuid" - }, - "title": "Paths", - "type": "object" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "root": { - "$ref": "#/components/schemas/WireNode" - }, - "truncated": { - "title": "Truncated", - "type": "boolean" - }, - "unaddressable": { - "additionalProperties": { - "type": "string" - }, - "description": "Nodes that could not be given a dotted path, keyed by node id, with the reason: `empty_name` (the node, or an ancestor, has no name \u2014 legal while designing), `reserved_separator` (a name contains `.`), or `ambiguous` (two returned nodes would render the same path, so neither is safe to act on). These are data conditions a user fixes by renaming, not errors.", - "propertyNames": { - "format": "uuid" - }, - "title": "Unaddressable", - "type": "object" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branch", - "root", - "nodes", - "edges", - "cross_boundary_edges", - "depth", - "truncated", - "paths", - "unaddressable" - ], - "title": "SubtreeResponse", - "type": "object" - }, - "UpdateNodeDataDelta": { - "description": "Update a node's data, with key-presence partial semantics.\n\n`after` may be empty; whether a field is present in `after` distinguishes\n\"set this field\" from \"leave it unchanged\".", - "properties": { - "after": { - "$ref": "#/components/schemas/NodeData" - }, - "node_id": { - "format": "uuid", - "title": "Node Id", - "type": "string" - }, - "type": { - "const": "update_node_data", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type", - "node_id", - "after" - ], - "title": "UpdateNodeDataDelta", - "type": "object" - }, - "V1CreateBranchBody": { - "description": "Body for ``POST /v1/projects/{pid}/branches``. ``name`` defaults to the\nnext ``main-work[-N]`` for the caller, so a bodyless POST works.", - "properties": { - "name": { - "anyOf": [ - { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - } - }, - "title": "V1CreateBranchBody", - "type": "object" - }, - "V1CreateProjectBody": { - "description": "Body for ``POST /v1/projects``. Only ``name`` is required; ``language``\nand ``intent`` default server-side to ``python``/``cli`` (mirrored by the\nCLI ``hydrate new`` defaults).", - "properties": { - "intent": { - "anyOf": [ - { - "maxLength": 40, - "type": "string" - }, - { - "type": "null" - } - ], - "default": "cli", - "description": "What the project is for. Defaults to 'cli'.", - "title": "Intent" - }, - "language": { - "anyOf": [ - { - "maxLength": 40, - "type": "string" - }, - { - "type": "null" - } - ], - "default": "python", - "description": "Primary language. Defaults to 'python'.", - "title": "Language" - }, - "name": { - "description": "Project name. Unique per-user, case-insensitive.", - "maxLength": 200, - "minLength": 1, - "title": "Name", - "type": "string" - } - }, - "required": [ - "name" - ], - "title": "V1CreateProjectBody", - "type": "object" - }, - "V1DeltasBody": { - "description": "Request body for ``POST /v1/branches/{bid}/deltas``.", - "properties": { - "deltas": { - "description": "Ordered list of delta operations to apply. Each must carry a `type` field in the closed write-surface vocabulary (add_node / delete_node / update_node_data / reparent_node / add_edge / delete_edge / flatten_boundary). Empty list returns 200 with `applied: false` and no version bump.", - "items": { - "discriminator": { - "mapping": { - "add_edge": "#/components/schemas/AddEdgeDelta", - "add_node": "#/components/schemas/AddNodeDelta", - "delete_edge": "#/components/schemas/DeleteEdgeDelta", - "delete_node": "#/components/schemas/DeleteNodeDelta", - "flatten_boundary": "#/components/schemas/FlattenBoundaryDelta", - "reparent_node": "#/components/schemas/ReparentNodeDelta", - "update_node_data": "#/components/schemas/UpdateNodeDataDelta" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/components/schemas/AddNodeDelta" - }, - { - "$ref": "#/components/schemas/DeleteNodeDelta" - }, - { - "$ref": "#/components/schemas/UpdateNodeDataDelta" - }, - { - "$ref": "#/components/schemas/ReparentNodeDelta" - }, - { - "$ref": "#/components/schemas/AddEdgeDelta" - }, - { - "$ref": "#/components/schemas/DeleteEdgeDelta" - }, - { - "$ref": "#/components/schemas/FlattenBoundaryDelta" - } - ] - }, - "title": "Deltas", - "type": "array" - }, - "expected_version": { - "description": "The branch.version the client believes is current. If it doesn't match the server's view at commit time, the route 409s with the actual current_version in the detail.", - "minimum": 0.0, - "title": "Expected Version", - "type": "integer" - }, - "positions": { - "anyOf": [ - { - "additionalProperties": { - "$ref": "#/components/schemas/V1NodePosition" - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "Optional explicit cell pins as `node_id -> {x, y}`, applied to the branch owner's view AFTER the deltas. **Additive**: a pin is introduced only for a node the owner hasn't already placed \u2014 it never overwrites a hand-placement and never deletes. Use only to override the derived grid placement for nodes you create.", - "title": "Positions" - } - }, - "required": [ - "expected_version" - ], - "title": "V1DeltasBody", - "type": "object" - }, - "V1NodePosition": { - "description": "A single explicit pin coordinate on the `/v1/` write surface.", - "properties": { - "x": { - "title": "X", - "type": "number" - }, - "y": { - "title": "Y", - "type": "number" - } - }, - "required": [ - "x", - "y" - ], - "title": "V1NodePosition", - "type": "object" - }, - "V1PatchProjectBody": { - "description": "Body for ``PATCH /v1/projects/{project_id}``.\n\nDeliberately narrower than the legacy session router's patch body, which\nalso carries ``language``, ``intent`` and ``layout_mode``. Those are\ncreation-time metadata on the agent surface; widening this later is\nadditive, whereas shipping fields the CLI has no verb for is not.", - "properties": { - "archived": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "description": "Archive or restore. Archived projects drop out of `GET /v1/projects` but stay reachable by id.", - "title": "Archived" - }, - "name": { - "anyOf": [ - { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - { - "type": "null" - } - ], - "description": "New name. Must be unique among your active projects (case-insensitive).", - "title": "Name" - } - }, - "title": "V1PatchProjectBody", - "type": "object" - }, - "V1ValidateBody": { - "description": "Request body for ``POST /v1/branches/{bid}/validate``.", - "properties": { - "deltas": { - "items": { - "discriminator": { - "mapping": { - "add_edge": "#/components/schemas/AddEdgeDelta", - "add_node": "#/components/schemas/AddNodeDelta", - "delete_edge": "#/components/schemas/DeleteEdgeDelta", - "delete_node": "#/components/schemas/DeleteNodeDelta", - "flatten_boundary": "#/components/schemas/FlattenBoundaryDelta", - "reparent_node": "#/components/schemas/ReparentNodeDelta", - "update_node_data": "#/components/schemas/UpdateNodeDataDelta" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/components/schemas/AddNodeDelta" - }, - { - "$ref": "#/components/schemas/DeleteNodeDelta" - }, - { - "$ref": "#/components/schemas/UpdateNodeDataDelta" - }, - { - "$ref": "#/components/schemas/ReparentNodeDelta" - }, - { - "$ref": "#/components/schemas/AddEdgeDelta" - }, - { - "$ref": "#/components/schemas/DeleteEdgeDelta" - }, - { - "$ref": "#/components/schemas/FlattenBoundaryDelta" - } - ] - }, - "title": "Deltas", - "type": "array" - } - }, - "title": "V1ValidateBody", - "type": "object" - }, - "ValidateResponse": { - "additionalProperties": false, - "properties": { - "branch": { - "$ref": "#/components/schemas/BranchRef" - }, - "findings": { - "items": { - "$ref": "#/components/schemas/Finding" - }, - "title": "Findings", - "type": "array" - }, - "project_id": { - "format": "uuid", - "title": "Project Id", - "type": "string" - }, - "valid": { - "title": "Valid", - "type": "boolean" - }, - "version": { - "title": "Version", - "type": "string" - } - }, - "required": [ - "version", - "project_id", - "branch", - "valid" - ], - "title": "ValidateResponse", - "type": "object" - }, - "ValidationError": { - "properties": { - "ctx": { - "title": "Context", - "type": "object" - }, - "input": { - "title": "Input" - }, - "loc": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "title": "Location", - "type": "array" - }, - "msg": { - "title": "Message", - "type": "string" - }, - "type": { - "title": "Error Type", - "type": "string" - } - }, - "required": [ - "loc", - "msg", - "type" - ], - "title": "ValidationError", - "type": "object" - }, - "Verification": { - "description": "A test specification attached to a behavior node.", - "properties": { - "author": { - "enum": [ - "user", - "agent" - ], - "title": "Author", - "type": "string" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "text": { - "title": "Text", - "type": "string" - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Type" - } - }, - "required": [ - "id", - "author", - "text" - ], - "title": "Verification", - "type": "object" - }, - "VersionConflictBody": { - "additionalProperties": false, - "properties": { - "current_version": { - "title": "Current Version", - "type": "integer" - }, - "error": { - "const": "version_conflict", - "title": "Error", - "type": "string" - } - }, - "required": [ - "error", - "current_version" - ], - "title": "VersionConflictBody", - "type": "object" - }, - "WireEdge": { - "additionalProperties": false, - "description": "Read-path edge: `source`/`target` are server-derived NODE ids\n(absent from the client-authored `h2o.types.Edge`); `*_handle` are the\nPORT ids.", - "properties": { - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "source": { - "format": "uuid", - "title": "Source", - "type": "string" - }, - "source_handle": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Source Handle" - }, - "target": { - "format": "uuid", - "title": "Target", - "type": "string" - }, - "target_handle": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Target Handle" - } - }, - "required": [ - "id", - "source", - "target", - "source_handle", - "target_handle" - ], - "title": "WireEdge", - "type": "object" - }, - "WireNode": { - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/WireNodeData" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "kind": { - "enum": [ - "behavior", - "boundary", - "state", - "io", - "interface" - ], - "title": "Kind", - "type": "string" - }, - "parent_id": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Parent Id" - }, - "position": { - "$ref": "#/components/schemas/Position" - } - }, - "required": [ - "id", - "kind", - "parent_id", - "position", - "data" - ], - "title": "WireNode", - "type": "object" - }, - "WireNodeData": { - "additionalProperties": false, - "description": "Read-path node `data`. NOT `h2o.NodeData`: the response omits\n`kind`/`parent_id` (those live on the wrapper) and emits the boundary/\nexternal scalars matrix-conditionally \u2014 so the matrix fields are\noptional (absent = not-applicable-to-kind).", - "properties": { - "config": { - "items": { - "$ref": "#/components/schemas/WirePort" - }, - "title": "Config", - "type": "array" - }, - "constraints": { - "items": { - "type": "string" - }, - "title": "Constraints", - "type": "array" - }, - "description": { - "title": "Description", - "type": "string" - }, - "documentation_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Documentation Url" - }, - "external_kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "External Kind" - }, - "inputs": { - "items": { - "$ref": "#/components/schemas/WirePort" - }, - "title": "Inputs", - "type": "array" - }, - "is_external": { - "title": "Is External", - "type": "boolean" - }, - "is_test_node": { - "title": "Is Test Node", - "type": "boolean" - }, - "language": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Language" - }, - "name": { - "title": "Name", - "type": "string" - }, - "outputs": { - "items": { - "$ref": "#/components/schemas/WirePort" - }, - "title": "Outputs", - "type": "array" - }, - "path_prefix": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Path Prefix" - }, - "protocol": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Protocol" - }, - "source_decisions": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Source Decisions" - }, - "status": { - "title": "Status", - "type": "string" - }, - "user_kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "User Kind" - }, - "verifications": { - "items": { - "$ref": "#/components/schemas/WireVerification" - }, - "title": "Verifications", - "type": "array" - } - }, - "required": [ - "name", - "description", - "status", - "is_test_node", - "is_external" - ], - "title": "WireNodeData", - "type": "object" - }, - "WirePort": { - "additionalProperties": false, - "properties": { - "contract_name": { - "anyOf": [ - { - "maxLength": 500, - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Contract Name" - }, - "description": { - "default": "", - "title": "Description", - "type": "string" - }, - "external": { - "default": false, - "title": "External", - "type": "boolean" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "name": { - "default": "", - "title": "Name", - "type": "string" - }, - "type": { - "default": "", - "title": "Type", - "type": "string" - } - }, - "required": [ - "id" - ], - "title": "WirePort", - "type": "object" - }, - "WireVerification": { - "additionalProperties": false, - "properties": { - "author": { - "enum": [ - "user", - "agent" - ], - "title": "Author", - "type": "string" - }, - "id": { - "format": "uuid", - "title": "Id", - "type": "string" - }, - "text": { - "title": "Text", - "type": "string" - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Type" - } - }, - "required": [ - "id", - "author", - "text" - ], - "title": "WireVerification", - "type": "object" - } - }, - "securitySchemes": { - "Bearer": { - "description": "API key. Send as `Authorization: Bearer `.", - "scheme": "bearer", - "type": "http" - } - } - }, - "info": { - "description": "The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication).\n\n**Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`.", - "title": "hydrate.sh v1 API", - "version": "1.0.0-rc.1" - }, - "openapi": "3.1.0", - "paths": { - "/v1/__healthz": { - "get": { - "description": "Returns `{\"ok\": true}` when the /v1/ surface is serving traffic. Unauthenticated. Use this for load-balancer and uptime-monitor checks; a 404 from this path means the /v1/ surface is disabled on the responding instance, not that the server is down.", - "operationId": "healthz_v1___healthz_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HealthzResponse" - } - } - }, - "description": "Successful Response" - } - }, - "security": [], - "summary": "Liveness probe", - "tags": [ - "health" - ] - } - }, - "/v1/branches/{branch_id}/boundary/{node_id}": { - "get": { - "description": "Returns a boundary node, its direct children, and the edges between those children \u2014 on this branch. Edges that cross the boundary belong to the parent cell's inspection and are not returned.", - "operationId": "fetch_branch_boundary_v1_branches__branch_id__boundary__node_id__get", - "parameters": [ - { - "description": "The branch to read.", - "in": "path", - "name": "branch_id", - "required": true, - "schema": { - "description": "The branch to read.", - "format": "uuid", - "title": "Branch Id", - "type": "string" - } - }, - { - "description": "The boundary node to inspect.", - "in": "path", - "name": "node_id", - "required": true, - "schema": { - "description": "The boundary node to inspect.", - "format": "uuid", - "title": "Node Id", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BoundaryResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Read a boundary's children and interior edges on a branch", - "tags": [ - "branches" - ] - } - }, - "/v1/branches/{branch_id}/deltas": { - "post": { - "description": "Apply an ordered batch of structural changes to a branch under optimistic concurrency. Pass the `version` from the branch read as `expected_version`; a 409 with `current_version` is returned if the branch moved underneath you. Pair with the `Idempotency-Key` header for at-most-once apply across retries.", - "operationId": "apply_branch_deltas_v1_branches__branch_id__deltas_post", - "parameters": [ - { - "description": "The branch to apply to.", - "in": "path", - "name": "branch_id", - "required": true, - "schema": { - "description": "The branch to apply to.", - "format": "uuid", - "title": "Branch Id", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/V1DeltasBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeltaApplyResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "oneOf": [ - { - "properties": { - "code": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - }, - { - "$ref": "#/components/schemas/NotFoundBody" - } - ] - } - }, - "required": [ - "detail" - ], - "type": "object" - } - } - }, - "description": "Branch/project not found or not accessible, OR a delta targets a node that doesn't exist." - }, - "409": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "oneOf": [ - { - "$ref": "#/components/schemas/VersionConflictBody" - }, - { - "$ref": "#/components/schemas/BranchNotActiveBody" - } - ] - } - }, - "required": [ - "detail" - ], - "type": "object" - } - } - }, - "description": "OCC version conflict, or the branch is not active." - }, - "422": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/HTTPValidationError" - }, - { - "properties": { - "detail": { - "$ref": "#/components/schemas/DeltaFieldErrorBody" - } - }, - "required": [ - "detail" - ], - "type": "object" - } - ] - } - } - }, - "description": "Request body failed validation, OR a delta is malformed / unknown / not an object." - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Apply a delta batch to a branch (OCC)", - "tags": [ - "branches" - ] - } - }, - "/v1/branches/{branch_id}/graph": { - "get": { - "description": "Read the full graph (nodes, ports, edges, version) of a specific branch. Use the returned `version` as `expected_version` on a subsequent apply to that branch.", - "operationId": "fetch_branch_graph_v1_branches__branch_id__graph_get", - "parameters": [ - { - "description": "The branch to read.", - "in": "path", - "name": "branch_id", - "required": true, - "schema": { - "description": "The branch to read.", - "format": "uuid", - "title": "Branch Id", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GraphResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Read a specific branch's graph", - "tags": [ - "branches" - ] - } - }, - "/v1/branches/{branch_id}/node/{node_id}": { - "get": { - "description": "Returns a node, every node one edge away from it, and those edges \u2014 on this branch. The scoped read an agent uses to load just the context around what it is about to change.", - "operationId": "fetch_branch_node_v1_branches__branch_id__node__node_id__get", - "parameters": [ - { - "description": "The branch to read.", - "in": "path", - "name": "branch_id", - "required": true, - "schema": { - "description": "The branch to read.", - "format": "uuid", - "title": "Branch Id", - "type": "string" - } - }, - { - "description": "The node to read.", - "in": "path", - "name": "node_id", - "required": true, - "schema": { - "description": "The node to read.", - "format": "uuid", - "title": "Node Id", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NodeNeighborhoodResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Read a node and its 1-hop neighborhood on a specific branch", - "tags": [ - "branches" - ] - } - }, - "/v1/branches/{branch_id}/subtree/{node_id}": { - "get": { - "description": "Returns a node and its descendants down to `depth` levels on this branch, with the edges among them. The scoped counterpart to `GET /v1/branches/{branch_id}/graph`: bounded by `depth` rather than by the size of the branch, so reading a slice of a large project never puts the whole graph on the wire.", - "operationId": "fetch_branch_subtree_v1_branches__branch_id__subtree__node_id__get", - "parameters": [ - { - "description": "The branch to read.", - "in": "path", - "name": "branch_id", - "required": true, - "schema": { - "description": "The branch to read.", - "format": "uuid", - "title": "Branch Id", - "type": "string" - } - }, - { - "description": "The node to root the subtree at.", - "in": "path", - "name": "node_id", - "required": true, - "schema": { - "description": "The node to root the subtree at.", - "format": "uuid", - "title": "Node Id", - "type": "string" - } - }, - { - "description": "How many levels of descendants to include. 1 = direct children only.", - "in": "query", - "name": "depth", - "required": false, - "schema": { - "default": 1, - "description": "How many levels of descendants to include. 1 = direct children only.", - "maximum": 32, - "minimum": 1, - "title": "Depth", - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SubtreeResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Read a node's subtree on a specific branch, to a bounded depth", - "tags": [ - "branches" - ] - } - }, - "/v1/branches/{branch_id}/validate": { - "post": { - "description": "Dry-run a delta batch and get back the full coherence report over the graph that would result \u2014 never mutating the branch. Returns 200 with `{valid, findings}`: `findings` is the complete list of coherence problems (an input port with no incoming edge, a wire pinned to a missing port, or an edge whose endpoint types differ), and `valid` is true only when there are no error-severity findings. The verdict is in the body, so an incoherent graph is still a 200. A 4xx is returned only for a request that can't be processed at all (a malformed body or an unknown delta type). An empty delta list reports the branch's current coherence.", - "operationId": "validate_branch_deltas_v1_branches__branch_id__validate_post", - "parameters": [ - { - "description": "The branch to validate against.", - "in": "path", - "name": "branch_id", - "required": true, - "schema": { - "description": "The branch to validate against.", - "format": "uuid", - "title": "Branch Id", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/V1ValidateBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidateResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "content": { - "application/json": { - "schema": { - "properties": { - "detail": { - "oneOf": [ - { - "properties": { - "code": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - }, - { - "$ref": "#/components/schemas/NotFoundBody" - } - ] - } - }, - "required": [ - "detail" - ], - "type": "object" - } - } - }, - "description": "Branch/project not found or not accessible, OR a delta targets a node that doesn't exist." - }, - "422": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/HTTPValidationError" - }, - { - "properties": { - "detail": { - "$ref": "#/components/schemas/DeltaFieldErrorBody" - } - }, - "required": [ - "detail" - ], - "type": "object" - } - ] - } - } - }, - "description": "Request body failed validation, OR a delta is malformed / unknown / not an object." - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Dry-run a delta batch and report coherence findings", - "tags": [ - "branches" - ] - } - }, - "/v1/graph/{project_id}": { - "get": { - "description": "Returns the active main-branch graph (nodes, ports, edges, version) for the project. Read-only: the main branch is merge-only. To make changes, fork a branch (`POST /v1/projects/{project_id}/branches`) and apply deltas to it.", - "operationId": "fetch_graph_v1_graph__project_id__get", - "parameters": [ - { - "description": "The project whose main-branch graph to fetch.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project whose main-branch graph to fetch.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GraphResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Get the canonical graph for a project", - "tags": [ - "graph" - ] - } - }, - "/v1/graph/{project_id}/boundary/{node_id}": { - "get": { - "description": "Returns the boundary view for a single node: the inputs and outputs that cross the node's enforcement perimeter. Used by clients that visualize a node's contract surface in isolation from the rest of the graph.", - "operationId": "fetch_boundary_v1_graph__project_id__boundary__node_id__get", - "parameters": [ - { - "description": "The project the boundary lives in.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project the boundary lives in.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - }, - { - "description": "The boundary node to inspect.", - "in": "path", - "name": "node_id", - "required": true, - "schema": { - "description": "The boundary node to inspect.", - "format": "uuid", - "title": "Node Id", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BoundaryResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Get the boundary topology around a node", - "tags": [ - "graph" - ] - } - }, - "/v1/graph/{project_id}/node/{node_id}": { - "get": { - "description": "Returns a single node along with its directly connected neighbors and edges (the 1-hop topology). The node must belong to the project's active main branch.", - "operationId": "fetch_node_with_neighbors_v1_graph__project_id__node__node_id__get", - "parameters": [ - { - "description": "The project the node lives in.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project the node lives in.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - }, - { - "description": "The node to fetch.", - "in": "path", - "name": "node_id", - "required": true, - "schema": { - "description": "The node to fetch.", - "format": "uuid", - "title": "Node Id", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NodeNeighborhoodResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Get a node with its 1-hop neighborhood", - "tags": [ - "graph" - ] - } - }, - "/v1/graph/{project_id}/subtree/{node_id}": { - "get": { - "description": "Returns a node and its descendants down to ``depth`` levels, with the edges among them. The scoped read for working inside a large project: unlike the full graph read, the response is bounded by ``depth`` rather than by the size of the branch, so a slice of a big graph never puts the whole graph on the wire.", - "operationId": "fetch_subtree_v1_graph__project_id__subtree__node_id__get", - "parameters": [ - { - "description": "The project the node lives in.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project the node lives in.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - }, - { - "description": "The node to root the subtree at.", - "in": "path", - "name": "node_id", - "required": true, - "schema": { - "description": "The node to root the subtree at.", - "format": "uuid", - "title": "Node Id", - "type": "string" - } - }, - { - "description": "How many levels of descendants to include. 1 = direct children only. Levels below the cut are omitted and ``truncated`` reports that they exist.", - "in": "query", - "name": "depth", - "required": false, - "schema": { - "default": 1, - "description": "How many levels of descendants to include. 1 = direct children only. Levels below the cut are omitted and ``truncated`` reports that they exist.", - "maximum": 32, - "minimum": 1, - "title": "Depth", - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SubtreeResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Get a node's subtree to a bounded depth", - "tags": [ - "graph" - ] - } - }, - "/v1/projects": { - "get": { - "description": "Returns the projects the caller can see. For API-key callers the list is further filtered by the per-key project allowlist (if configured). Archived projects are excluded. An empty list is a normal response when the caller has no visible projects. Archived projects are excluded unless `include_archived=true`.", - "operationId": "list_projects_v1_projects_get", - "parameters": [ - { - "description": "Maximum projects to return. Defaults to 100; max 200.", - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 100, - "description": "Maximum projects to return. Defaults to 100; max 200.", - "maximum": 200, - "minimum": 1, - "title": "Limit", - "type": "integer" - } - }, - { - "description": "Include archived projects. Off by default. Needed by name-addressed callers such as the CLI: an archived project that cannot be listed cannot be renamed or restored, which would make archiving a one-way door.", - "in": "query", - "name": "include_archived", - "required": false, - "schema": { - "default": false, - "description": "Include archived projects. Off by default. Needed by name-addressed callers such as the CLI: an archived project that cannot be listed cannot be renamed or restored, which would make archiving a one-way door.", - "title": "Include Archived", - "type": "boolean" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectsListResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "List your projects", - "tags": [ - "projects" - ] - }, - "post": { - "description": "Create a new project owned by the caller. Creating a project also seeds its protected `main` branch in the same transaction, so the returned `main_branch` can be forked immediately (via `POST /v1/projects/{project_id}/branches`) with no second round-trip. The name must be unique among your active projects (case-insensitive); a collision returns 409 `name_taken`. Requires the `graph:write` scope.", - "operationId": "create_project_v1_projects_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/V1CreateProjectBody" - } - } - }, - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectCreateResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "409": { - "$ref": "#/components/responses/ProjectCreateConflict" - }, - "422": { - "$ref": "#/components/responses/ProjectCreateUnprocessable" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Create a project", - "tags": [ - "projects" - ] - } - }, - "/v1/projects/{project_id}": { - "delete": { - "description": "Permanently delete a project you own, along with its branches, graph and stored artifacts. This cannot be undone.\n\nRequires the `project:delete` scope, which is **separate from `graph:write` and is not implied by it** \u2014 a key that can author a graph cannot erase one unless it was minted with this scope as well. Keys issued before this scope existed do not have it and must be re-minted.\n\nOnly the project's owner may delete it. A caller who can see the project but does not own it gets the same `404` a non-existent project gets, so the response never reveals whether a project exists.", - "operationId": "delete_project_v1_projects__project_id__delete", - "parameters": [ - { - "description": "The project to delete.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project to delete.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Delete a project", - "tags": [ - "projects" - ] - }, - "patch": { - "description": "Rename a project you own, archive it, or restore it. At least one of `name` or `archived` must be present; an empty body is rejected with `422 no_fields` rather than reporting success for a request that changed nothing.\n\nArchiving is the non-destructive alternative to deletion: the project leaves `GET /v1/projects` but remains addressable by id and can be restored. It does **not** free a slot against your project cap \u2014 only deletion does.\n\nRequires `graph:write` \u2014 deliberately NOT a scope of its own, unlike `project:delete`. That scope exists because deletion is irreversible; both edits here round-trip, so an authoring key holding them cannot destroy anything it cannot also put back. The consequence is that every existing `graph:write` key gains rename and archive when this ships, with no re-mint.\n\nOnly the owner may modify a project; a caller who can see it but does not own it gets the same `404` a non-existent project gets.", - "operationId": "patch_project_v1_projects__project_id__patch", - "parameters": [ - { - "description": "The project to modify.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project to modify.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/V1PatchProjectBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectPatchResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/ProjectPatchConflict" - }, - "422": { - "$ref": "#/components/responses/ProjectPatchUnprocessable" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Rename or archive a project", - "tags": [ - "projects" - ] - } - }, - "/v1/projects/{project_id}/branches": { - "get": { - "description": "List your active working branches on this project. The protected main branch is not included. Each branch is a unit of work you can read and apply changes to before merging into main.", - "operationId": "list_branches_v1_projects__project_id__branches_get", - "parameters": [ - { - "description": "The project to list branches for.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project to list branches for.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BranchListResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "List the caller's working branches on a project", - "tags": [ - "branches" - ] - }, - "post": { - "description": "Fork a new working branch from the project's main branch. The branch is copied from main's current state and owned by you; your changes stay isolated on it until merged. Requires an editor role, and there is a per-project limit on how many active branches you can hold at once.", - "operationId": "create_branch_v1_projects__project_id__branches_post", - "parameters": [ - { - "description": "The project to fork a branch in.", - "in": "path", - "name": "project_id", - "required": true, - "schema": { - "description": "The project to fork a branch in.", - "format": "uuid", - "title": "Project Id", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/V1CreateBranchBody" - } - } - }, - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BranchCreateResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "$ref": "#/components/responses/Unauthenticated" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - }, - "summary": "Start a unit of work (fork a branch from main)", - "tags": [ - "branches" - ] - } - } - }, - "security": [ - { - "Bearer": [] - } - ], - "servers": [ - { - "description": "Production", - "url": "https://api.hydrate.sh" - } - ], - "tags": [ - { - "description": "Work on a branch (a unit of work) rather than the protected main branch: fork a branch from main, list your branches, and read / validate / apply deltas against a specific branch.", - "name": "branches" - }, - { - "description": "Read the canonical graph for a project, validate a delta batch (dry-run), or apply a delta batch through the OCC + idempotency middleware chain.", - "name": "graph" - }, - { - "description": "Liveness probes. Unauthenticated. Used by load balancers and uptime monitors.", - "name": "health" - }, - { - "description": "List the projects your principal can see. Filtered by membership + (for API keys) the per-key project allowlist.", - "name": "projects" - } - ] -} +{"components": {"responses": {"Forbidden": {"content": {"application/json": {"schema": {"properties": {"detail": {"properties": {"code": {"type": "string"}, "message": {"type": "string"}}, "type": "object"}}, "type": "object"}}}, "description": "Credentials are valid but lack the scope required for this route, or the principal lacks membership in the target project."}, "NotFound": {"content": {"application/json": {"schema": {"properties": {"detail": {"properties": {"code": {"example": "not_found", "type": "string"}, "message": {"type": "string"}}, "type": "object"}}, "type": "object"}}}, "description": "Resource not found OR not accessible to this principal. Leak parity: the response is identical in both cases so an attacker cannot enumerate resources via 404-vs-403 timing."}, "ProjectCreateConflict": {"content": {"application/json": {"schema": {"properties": {"detail": {"properties": {"code": {"enum": ["name_taken", "project_limit_reached"], "type": "string"}, "message": {"type": "string"}}, "required": ["code", "message"], "type": "object"}}, "type": "object"}}}, "description": "The project could not be created because it conflicts with existing state. ``name_taken`` \u2014 an active project of yours already uses this name (case-insensitive); pick another. ``project_limit_reached`` \u2014 you are at the per-user project cap, which counts ARCHIVED projects too; delete a project to free a slot (archiving does not)."}, "ProjectCreateUnprocessable": {"content": {"application/json": {"schema": {"properties": {"detail": {"properties": {"code": {"enum": ["invalid_name", "unsupported_language", "unsupported_intent"], "type": "string"}, "message": {"type": "string"}}, "required": ["code", "message"], "type": "object"}}, "type": "object"}}}, "description": "A field in the request body is not acceptable. ``invalid_name`` \u2014 the name is blank after trimming. ``unsupported_language`` / ``unsupported_intent`` \u2014 the value is outside the server's allowlist. NOTE: this envelope is ``{detail: {code, message}}``, NOT FastAPI's ``HTTPValidationError``."}, "RateLimited": {"content": {"application/json": {"schema": {"properties": {"detail": {"example": "rate_limited", "type": "string"}}, "type": "object"}}}, "description": "Per-bucket rate limit exceeded. The response carries ``Retry-After`` and the standard ``X-RateLimit-*`` headers (Limit / Remaining / Reset).", "headers": {"Retry-After": {"schema": {"type": "integer"}}, "X-RateLimit-Limit": {"schema": {"type": "integer"}}, "X-RateLimit-Remaining": {"schema": {"type": "integer"}}, "X-RateLimit-Reset": {"schema": {"type": "integer"}}}}, "Unauthenticated": {"content": {"application/json": {"schema": {"properties": {"detail": {"example": "unauthenticated", "type": "string"}}, "required": ["detail"], "type": "object"}}}, "description": "No credentials, malformed credentials, or revoked credentials. The envelope is leak-parity (same shape across all 401 paths) so an attacker cannot distinguish revoked vs. unknown via the body."}, "Unprocessable": {"content": {"application/json": {"schema": {"properties": {"detail": {"oneOf": [{"type": "string"}, {"properties": {"delta_ordinal": {"type": "integer"}, "delta_type": {"type": ["string", "null"]}, "error": {"type": "string"}, "field": {"type": ["string", "null"]}, "reason": {"type": ["string", "null"]}, "recoverable": {"type": "boolean"}, "recovery_hint": {"type": ["string", "null"]}}, "type": "object"}]}}, "type": "object"}}}, "description": "Request body failed validation. For delta routes the response carries ``error``, ``delta_ordinal``, ``delta_type``, ``field``, ``reason``, ``recoverable``, and ``recovery_hint`` so a client can auto-recover or surface a typed error."}, "VersionConflict": {"content": {"application/json": {"schema": {"properties": {"detail": {"properties": {"current_version": {"type": "integer"}, "error": {"example": "version_conflict", "type": "string"}}, "type": "object"}}, "type": "object"}}}, "description": "Optimistic concurrency check failed \u2014 the branch's current version is not the one the client sent in ``expected_version``. The response carries ``current_version`` so the client can refetch + retry."}}, "schemas": {"AddEdgeDelta": {"description": "Insert an edge.", "properties": {"edge": {"$ref": "#/components/schemas/Edge"}, "type": {"const": "add_edge", "title": "Type", "type": "string"}}, "required": ["type", "edge"], "title": "AddEdgeDelta", "type": "object"}, "AddNodeDelta": {"description": "Insert a node.", "properties": {"node": {"$ref": "#/components/schemas/Node"}, "type": {"const": "add_node", "title": "Type", "type": "string"}}, "required": ["type", "node"], "title": "AddNodeDelta", "type": "object"}, "AnchorRef": {"additionalProperties": false, "description": "Where a decision is anchored. ``id`` is a free-text reference\nwhose meaning depends on ``type`` (a node/boundary UUID, an error\npayload key, or absent for a project-level decision).\n\n``type`` is an OPEN set, published as a plain string, for exactly the\nreason ``Finding.code`` is \u2014 see that model. The anchor vocabulary is\nserver-owned and grows additively as new things become anchorable, so\na consumer must tolerate a ``type`` it does not recognize.", "properties": {"id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"description": "What the decision is anchored to. Known values: `boundary`, `node`, `error_payload`, `project`. **This set is open and grows additively** \u2014 treat an unrecognized type as an opaque string and render it; do not fail on it.", "examples": ["node", "boundary", "project"], "title": "Type", "type": "string"}}, "required": ["type", "id"], "title": "AnchorRef", "type": "object"}, "AppliedDecisionFilters": {"additionalProperties": false, "description": "The narrowing the server ACTUALLY applied, echoed back.\n\nNot a copy of the query string: ``owner_layer`` omitted means \"your\nwhole altitude membership\", ``state`` omitted means \"every settled\nstate\", and neither is knowable client-side. Without the echo an\nempty page is indistinguishable from \"this project has no\ndecisions\", which is a different fact and sends the caller looking\nin the wrong place.", "properties": {"owner_layer": {"items": {"type": "string"}, "title": "Owner Layer", "type": "array"}, "state": {"items": {"type": "string"}, "title": "State", "type": "array"}}, "required": ["owner_layer", "state"], "title": "AppliedDecisionFilters", "type": "object"}, "BoundaryResponse": {"additionalProperties": false, "properties": {"boundary": {"$ref": "#/components/schemas/WireNode"}, "branch": {"$ref": "#/components/schemas/BranchRef"}, "children": {"items": {"$ref": "#/components/schemas/WireNode"}, "title": "Children", "type": "array"}, "edges": {"items": {"$ref": "#/components/schemas/WireEdge"}, "title": "Edges", "type": "array"}, "paths": {"additionalProperties": {"type": "string"}, "description": "Dotted path for every node returned that has one, keyed by node id. Server-rendered because a scoped read returns a SLICE: the ancestors a path is built from are not in the payload, so a client cannot derive it. Every returned node appears in exactly one of `paths` or `unaddressable` \u2014 index this map for anything not listed there.", "propertyNames": {"format": "uuid"}, "title": "Paths", "type": "object"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "unaddressable": {"additionalProperties": {"type": "string"}, "description": "Nodes that could not be given a dotted path, keyed by node id, with the reason: `empty_name` (the node, or an ancestor, has no name \u2014 legal while designing), `reserved_separator` (a name contains `.`), or `ambiguous` (two returned nodes would render the same path, so neither is safe to act on). These are data conditions a user fixes by renaming, not errors.", "propertyNames": {"format": "uuid"}, "title": "Unaddressable", "type": "object"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch", "boundary", "children", "edges", "paths", "unaddressable"], "title": "BoundaryResponse", "type": "object"}, "BranchCreateResponse": {"additionalProperties": false, "properties": {"branch": {"$ref": "#/components/schemas/BranchMeta"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch"], "title": "BranchCreateResponse", "type": "object"}, "BranchListResponse": {"additionalProperties": false, "properties": {"branches": {"items": {"$ref": "#/components/schemas/BranchMeta"}, "title": "Branches", "type": "array"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branches"], "title": "BranchListResponse", "type": "object"}, "BranchMeta": {"additionalProperties": false, "properties": {"base_main_version": {"title": "Base Main Version", "type": "integer"}, "created_at": {"title": "Created At", "type": "string"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "is_main": {"title": "Is Main", "type": "boolean"}, "last_active_at": {"title": "Last Active At", "type": "string"}, "merged_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Merged At"}, "name": {"title": "Name", "type": "string"}, "owner_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Owner Id"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "status": {"title": "Status", "type": "string"}, "version": {"title": "Version", "type": "integer"}}, "required": ["id", "project_id", "owner_id", "name", "is_main", "version", "base_main_version", "status", "last_active_at", "created_at", "merged_at"], "title": "BranchMeta", "type": "object"}, "BranchNotActiveBody": {"additionalProperties": false, "properties": {"current_status": {"title": "Current Status", "type": "string"}, "error": {"const": "branch_not_active", "title": "Error", "type": "string"}}, "required": ["error", "current_status"], "title": "BranchNotActiveBody", "type": "object"}, "BranchPatchResponse": {"additionalProperties": false, "description": "Rename returns the branch alone \u2014 the project is not affected.", "properties": {"branch": {"$ref": "#/components/schemas/BranchMeta"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch"], "title": "BranchPatchResponse", "type": "object"}, "BranchRef": {"additionalProperties": false, "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "version": {"title": "Version", "type": "integer"}}, "required": ["id", "version"], "title": "BranchRef", "type": "object"}, "DecisionOut": {"additionalProperties": false, "description": "A decision as the /v1 surface serves it.\n\n**Deliberately narrower than the in-app serializer.** The fields it\nomits are omitted for a reason, and the omission is the mitigation \u2014\nadding one back is a disclosure decision, not a serializer tweak:\n\n * ``history`` \u2014 the append-only audit trail, which names the actor\n of every transition. Accountability data for humans in the app,\n not context for an agent.\n * ``assignee_user_id`` \u2014 a user id, same reasoning.\n * ``draft_ids`` / ``originating_interview_ref`` \u2014 pointers into\n interview transcripts, which are the candid text itself.\n * ``interview_session`` \u2014 the transcript. Never.\n * ``snoozed_until`` \u2014 in-app queue mechanics with no meaning to an\n external caller.\n * ``flag_reason`` \u2014 why a human flagged the row for approval. An\n in-app queue annotation about the *review*, not about the\n decision, and it names no behaviour an agent can act on.", "properties": {"anchor_ref": {"$ref": "#/components/schemas/AnchorRef"}, "annotations": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Annotations"}, "capture_text": {"title": "Capture Text", "type": "string"}, "captured_at": {"title": "Captured At", "type": "string"}, "check": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Check"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "owner_layer": {"title": "Owner Layer", "type": "string"}, "parent_decision_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Parent Decision Id"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "resulting_node_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Resulting Node Ids", "type": "array"}, "source_layer": {"title": "Source Layer", "type": "string"}, "state": {"title": "State", "type": "string"}}, "required": ["id", "project_id", "owner_layer", "source_layer", "anchor_ref", "state", "capture_text", "annotations", "check", "captured_at", "parent_decision_id", "resulting_node_ids"], "title": "DecisionOut", "type": "object"}, "DecisionResponse": {"additionalProperties": false, "properties": {"decision": {"$ref": "#/components/schemas/DecisionOut"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "decision"], "title": "DecisionResponse", "type": "object"}, "DecisionsListResponse": {"additionalProperties": false, "description": "One page of decisions for one project.\n\n``has_more`` is explicit rather than inferred from\n``len(decisions) == limit``: a page that happens to end exactly on\nthe boundary is indistinguishable from a full one, and a caller that\nguesses wrong either stops early or pages forever.\n\nIt is ADVISORY. The count and the page are two statements under read\ncommitted, so a decision captured between them can make ``has_more``\na row stale. Page until a page comes back short or empty rather than\ntrusting it as an invariant \u2014 and expect the ordering (newest first)\nto shift a row onto a page you have already read if the ledger is\nbeing written while you walk it.", "properties": {"decisions": {"items": {"$ref": "#/components/schemas/DecisionOut"}, "title": "Decisions", "type": "array"}, "filters": {"$ref": "#/components/schemas/AppliedDecisionFilters"}, "has_more": {"title": "Has More", "type": "boolean"}, "limit": {"title": "Limit", "type": "integer"}, "offset": {"title": "Offset", "type": "integer"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "decisions", "filters", "limit", "offset", "has_more"], "title": "DecisionsListResponse", "type": "object"}, "DeleteEdgeDelta": {"description": "Remove an edge.", "properties": {"edge_id": {"format": "uuid", "title": "Edge Id", "type": "string"}, "type": {"const": "delete_edge", "title": "Type", "type": "string"}}, "required": ["type", "edge_id"], "title": "DeleteEdgeDelta", "type": "object"}, "DeleteNodeDelta": {"description": "Cascade-delete a node and its descendant subtree.", "properties": {"node_id": {"format": "uuid", "title": "Node Id", "type": "string"}, "type": {"const": "delete_node", "title": "Type", "type": "string"}}, "required": ["type", "node_id"], "title": "DeleteNodeDelta", "type": "object"}, "DeltaApplyResponse": {"additionalProperties": false, "properties": {"applied": {"title": "Applied", "type": "boolean"}, "branch": {"$ref": "#/components/schemas/BranchRef"}, "delta_count": {"title": "Delta Count", "type": "integer"}, "positions_applied": {"anyOf": [{"type": "integer"}, {"type": "null"}], "default": null, "title": "Positions Applied"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch", "applied", "delta_count"], "title": "DeltaApplyResponse", "type": "object"}, "DeltaFieldErrorBody": {"additionalProperties": false, "description": "The 7-key per-delta envelope (always all keys present) the frontend\nauto-recovery consumes. Shared by every 422 delta error; `error`\ndiscriminates the cause.", "properties": {"delta_ordinal": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Delta Ordinal"}, "delta_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Delta Type"}, "error": {"enum": ["malformed_delta_field", "unknown_delta_type", "delta_not_object", "delta_too_large", "batch_too_large", "batch_count_exceeded"], "title": "Error", "type": "string"}, "field": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Field"}, "reason": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Reason"}, "recoverable": {"title": "Recoverable", "type": "boolean"}, "recovery_hint": {"anyOf": [{"$ref": "#/components/schemas/RecoveryHint"}, {"type": "null"}]}}, "required": ["error", "delta_ordinal", "delta_type", "field", "reason", "recoverable", "recovery_hint"], "title": "DeltaFieldErrorBody", "type": "object"}, "Edge": {"description": "An edge connecting two nodes' ports.\n\nEndpoints are given as `source_handle` / `target_handle`, each a port\nid. Unknown keys are ignored rather than rejected.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "source_handle": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "default": null, "title": "Source Handle"}, "target_handle": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "default": null, "title": "Target Handle"}}, "required": ["id"], "title": "Edge", "type": "object"}, "Finding": {"additionalProperties": false, "description": "A single coherence finding over the graph that results from a delta\nbatch. ``locator`` is the node / port / edge id (a string) the finding is\nabout.\n\n**``code`` is an OPEN set and is published as a plain string on purpose.**\nThe codes are owned by the server and new ones are added additively as new\ncoherence rules ship. Publishing the set as a closed enum made every\ngenerated client close it too \u2014 and a generated enum with no fallback arm\nfails to deserialize the WHOLE response the first time the server sends a\ncode the client has not heard of. That turns every additive rule into a\ncoordinated release, and turns a client that is merely out of date into one\nthat cannot read a validate response at all.\n\nSo the contract is: **a consumer must tolerate codes it does not\nrecognize** \u2014 render them, do not switch exhaustively on them. The known\nvalues are documented in the field description rather than enforced by the\nschema, because enforcing them here bought documentation at the cost of\nbreaking clients.\n\n``severity`` stays closed: it is a two-valued verdict a consumer genuinely\nmust branch on, and adding a third value WOULD be a breaking change that\ndeserves a coordinated release.", "properties": {"code": {"description": "Machine-readable finding code. Known values: `unsatisfied_input` (an input port with no incoming edge), `dangling_wire` (an edge whose endpoint does not exist), `type_mismatch` (a wired pair whose port types disagree). **This set is open and grows additively** \u2014 treat an unrecognized code as an opaque string and render it; do not fail on it.", "examples": ["unsatisfied_input", "dangling_wire", "type_mismatch"], "title": "Code", "type": "string"}, "locator": {"title": "Locator", "type": "string"}, "message": {"title": "Message", "type": "string"}, "severity": {"enum": ["error", "warning"], "title": "Severity", "type": "string"}}, "required": ["code", "severity", "locator", "message"], "title": "Finding", "type": "object"}, "FlattenBoundaryDelta": {"description": "Flatten a boundary node: delete it and promote its children to its parent.", "properties": {"node_id": {"format": "uuid", "title": "Node Id", "type": "string"}, "type": {"const": "flatten_boundary", "title": "Type", "type": "string"}}, "required": ["type", "node_id"], "title": "FlattenBoundaryDelta", "type": "object"}, "GraphResponse": {"additionalProperties": false, "properties": {"branch": {"$ref": "#/components/schemas/BranchRef"}, "edges": {"items": {"$ref": "#/components/schemas/WireEdge"}, "title": "Edges", "type": "array"}, "nodes": {"items": {"$ref": "#/components/schemas/WireNode"}, "title": "Nodes", "type": "array"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch", "nodes", "edges"], "title": "GraphResponse", "type": "object"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "title": "Detail", "type": "array"}}, "title": "HTTPValidationError", "type": "object"}, "HealthzResponse": {"additionalProperties": false, "properties": {"ok": {"title": "Ok", "type": "boolean"}}, "required": ["ok"], "title": "HealthzResponse", "type": "object"}, "MainBranchRef": {"additionalProperties": false, "description": "The seeded main branch as returned by project-create.\n\nDeliberately narrower than ``BranchMeta``: create returns only what the\nclient can't mint locally and needs for its next call. Pinning the real\nshape (not ``BranchMeta``) keeps the generated SDK honest.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "version": {"title": "Version", "type": "integer"}}, "required": ["id", "version"], "title": "MainBranchRef", "type": "object"}, "Node": {"description": "A behavior or boundary node.", "properties": {"data": {"$ref": "#/components/schemas/NodeData"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "kind": {"enum": ["behavior", "boundary", "state", "io", "interface"], "title": "Kind", "type": "string"}, "parent_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "default": null, "title": "Parent Id"}}, "required": ["id", "kind"], "title": "Node", "type": "object"}, "NodeData": {"description": "A node's data payload; its shape varies by kind and whether the node is\nexternal.\n\nOn update, key-presence semantics apply: a field present with a null value is\nset to null, while an omitted field is left unchanged. All fields are optional\nat the type level \u2014 cross-field rules (e.g. behavior nodes reject `user_kind`;\nexternal nodes require `external_kind`) are enforced server-side, so a partial\nupdate is not blocked by \"missing required field\" errors that don't apply to it.", "properties": {"aliases": {"items": {"maxLength": 500, "type": "string"}, "maxItems": 50, "title": "Aliases", "type": "array"}, "config": {"items": {"$ref": "#/components/schemas/Port"}, "title": "Config", "type": "array"}, "constraints": {"items": {"type": "string"}, "title": "Constraints", "type": "array"}, "description": {"default": "", "title": "Description", "type": "string"}, "documentation_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Documentation Url"}, "external_kind": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "External Kind"}, "inputs": {"items": {"$ref": "#/components/schemas/Port"}, "title": "Inputs", "type": "array"}, "is_external": {"default": false, "title": "Is External", "type": "boolean"}, "is_test_node": {"default": false, "title": "Is Test Node", "type": "boolean"}, "kind": {"anyOf": [{"enum": ["behavior", "boundary", "state", "io", "interface"], "type": "string"}, {"type": "null"}], "default": null, "title": "Kind"}, "language": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Language"}, "name": {"default": "", "title": "Name", "type": "string"}, "outputs": {"items": {"$ref": "#/components/schemas/Port"}, "title": "Outputs", "type": "array"}, "parent_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "default": null, "title": "Parent Id"}, "path_prefix": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Path Prefix"}, "protocol": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Protocol"}, "source_decisions": {"items": {"format": "uuid", "type": "string"}, "title": "Source Decisions", "type": "array"}, "status": {"default": "idle", "title": "Status", "type": "string"}, "user_kind": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "User Kind"}, "verifications": {"items": {"$ref": "#/components/schemas/Verification"}, "title": "Verifications", "type": "array"}}, "title": "NodeData", "type": "object"}, "NodeNeighborhoodResponse": {"additionalProperties": false, "properties": {"branch": {"$ref": "#/components/schemas/BranchRef"}, "edges": {"items": {"$ref": "#/components/schemas/WireEdge"}, "title": "Edges", "type": "array"}, "neighbors": {"items": {"$ref": "#/components/schemas/WireNode"}, "title": "Neighbors", "type": "array"}, "node": {"$ref": "#/components/schemas/WireNode"}, "paths": {"additionalProperties": {"type": "string"}, "description": "Dotted path for every node returned that has one, keyed by node id. Server-rendered because a scoped read returns a SLICE: the ancestors a path is built from are not in the payload, so a client cannot derive it. Every returned node appears in exactly one of `paths` or `unaddressable` \u2014 index this map for anything not listed there.", "propertyNames": {"format": "uuid"}, "title": "Paths", "type": "object"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "unaddressable": {"additionalProperties": {"type": "string"}, "description": "Nodes that could not be given a dotted path, keyed by node id, with the reason: `empty_name` (the node, or an ancestor, has no name \u2014 legal while designing), `reserved_separator` (a name contains `.`), or `ambiguous` (two returned nodes would render the same path, so neither is safe to act on). These are data conditions a user fixes by renaming, not errors.", "propertyNames": {"format": "uuid"}, "title": "Unaddressable", "type": "object"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch", "node", "neighbors", "edges", "paths", "unaddressable"], "title": "NodeNeighborhoodResponse", "type": "object"}, "NotFoundBody": {"additionalProperties": false, "description": "Returned when a delta targets a node that doesn't exist (404). `node_id`\nidentifies the missing target.", "properties": {"error": {"const": "not_found", "title": "Error", "type": "string"}, "node_id": {"title": "Node Id", "type": "string"}, "reason": {"title": "Reason", "type": "string"}}, "required": ["error", "node_id", "reason"], "title": "NotFoundBody", "type": "object"}, "Port": {"description": "An input, output, or config port on a node.", "properties": {"contract_name": {"anyOf": [{"maxLength": 500, "type": "string"}, {"type": "null"}], "default": null, "title": "Contract Name"}, "description": {"default": "", "title": "Description", "type": "string"}, "external": {"default": false, "title": "External", "type": "boolean"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"default": "", "title": "Name", "type": "string"}, "type": {"default": "", "title": "Type", "type": "string"}}, "required": ["id"], "title": "Port", "type": "object"}, "Position": {"additionalProperties": false, "properties": {"x": {"title": "X", "type": "number"}, "y": {"title": "Y", "type": "number"}}, "required": ["x", "y"], "title": "Position", "type": "object"}, "ProjectCreateResponse": {"additionalProperties": false, "properties": {"main_branch": {"$ref": "#/components/schemas/MainBranchRef"}, "project": {"$ref": "#/components/schemas/ProjectOut"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project", "main_branch"], "title": "ProjectCreateResponse", "type": "object"}, "ProjectOut": {"additionalProperties": false, "properties": {"archived": {"title": "Archived", "type": "boolean"}, "created_at": {"title": "Created At", "type": "string"}, "h2o_schema_version": {"title": "H2O Schema Version", "type": "integer"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "intent": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Intent"}, "language": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Language"}, "last_opened_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Last Opened At"}, "name": {"title": "Name", "type": "string"}, "updated_at": {"title": "Updated At", "type": "string"}}, "required": ["id", "name", "language", "intent", "archived", "h2o_schema_version", "created_at", "updated_at", "last_opened_at"], "title": "ProjectOut", "type": "object"}, "ProjectPatchResponse": {"additionalProperties": false, "description": "Rename/archive returns the project alone.\n\nNo ``main_branch``, unlike create: the branch is not affected by either\nedit, and echoing it would imply otherwise.", "properties": {"project": {"$ref": "#/components/schemas/ProjectOut"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project"], "title": "ProjectPatchResponse", "type": "object"}, "ProjectsListResponse": {"additionalProperties": false, "properties": {"projects": {"items": {"$ref": "#/components/schemas/ProjectOut"}, "title": "Projects", "type": "array"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "projects"], "title": "ProjectsListResponse", "type": "object"}, "RecoveryHint": {"description": "The finite set of recovery hints a client may receive in\n`ApiError.recovery_hint`:\n\n - \"regenerate_uuid\" \u2014 mint a new UUID at `field` and retry\n - \"drop_field\" \u2014 delete `field` and retry\n - null \u2014 not auto-recoverable; user intervention needed\n\nA closed set, so integrations can switch over the value; a new hint is added\nhere deliberately, giving clients a chance to handle it.", "enum": ["regenerate_uuid", "drop_field"], "title": "RecoveryHint", "type": "string"}, "ReparentNodeDelta": {"description": "Move a node to a new parent (or to top-level).\n\n`parent_id` must be present on the wire (use null for top-level); an absent key\nis rejected, so a client bug surfaces as a clean parse error rather than an\nambiguous no-op.", "properties": {"node_id": {"format": "uuid", "title": "Node Id", "type": "string"}, "parent_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Parent Id"}, "type": {"const": "reparent_node", "title": "Type", "type": "string"}}, "required": ["type", "node_id", "parent_id"], "title": "ReparentNodeDelta", "type": "object"}, "SubtreeResponse": {"additionalProperties": false, "description": "A node plus its descendants to a bounded depth.\n\n``nodes`` EXCLUDES ``root``. ``truncated`` is true when the walk stopped\nwith descendants still below it \u2014 a caller that cannot distinguish a\ncomplete cell from a slice will treat a slice as complete, so the signal\nis explicit rather than inferred from ``depth``.\n\n``cross_boundary_edges`` carries edges with exactly one endpoint inside\nthe returned set: real dependencies of this slice, which a scoped read\nwould otherwise hide.", "properties": {"branch": {"$ref": "#/components/schemas/BranchRef"}, "cross_boundary_edges": {"items": {"$ref": "#/components/schemas/WireEdge"}, "title": "Cross Boundary Edges", "type": "array"}, "depth": {"title": "Depth", "type": "integer"}, "edges": {"items": {"$ref": "#/components/schemas/WireEdge"}, "title": "Edges", "type": "array"}, "nodes": {"items": {"$ref": "#/components/schemas/WireNode"}, "title": "Nodes", "type": "array"}, "paths": {"additionalProperties": {"type": "string"}, "description": "Dotted path for every node returned that has one, keyed by node id. Server-rendered because a scoped read returns a SLICE: the ancestors a path is built from are not in the payload, so a client cannot derive it. Every returned node appears in exactly one of `paths` or `unaddressable` \u2014 index this map for anything not listed there.", "propertyNames": {"format": "uuid"}, "title": "Paths", "type": "object"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "root": {"$ref": "#/components/schemas/WireNode"}, "truncated": {"title": "Truncated", "type": "boolean"}, "unaddressable": {"additionalProperties": {"type": "string"}, "description": "Nodes that could not be given a dotted path, keyed by node id, with the reason: `empty_name` (the node, or an ancestor, has no name \u2014 legal while designing), `reserved_separator` (a name contains `.`), or `ambiguous` (two returned nodes would render the same path, so neither is safe to act on). These are data conditions a user fixes by renaming, not errors.", "propertyNames": {"format": "uuid"}, "title": "Unaddressable", "type": "object"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch", "root", "nodes", "edges", "cross_boundary_edges", "depth", "truncated", "paths", "unaddressable"], "title": "SubtreeResponse", "type": "object"}, "UpdateNodeDataDelta": {"description": "Update a node's data, with key-presence partial semantics.\n\n`after` may be empty; whether a field is present in `after` distinguishes\n\"set this field\" from \"leave it unchanged\".", "properties": {"after": {"$ref": "#/components/schemas/NodeData"}, "node_id": {"format": "uuid", "title": "Node Id", "type": "string"}, "type": {"const": "update_node_data", "title": "Type", "type": "string"}}, "required": ["type", "node_id", "after"], "title": "UpdateNodeDataDelta", "type": "object"}, "V1CreateBranchBody": {"description": "Body for ``POST /v1/projects/{pid}/branches``. ``name`` defaults to the\nnext ``main-work[-N]`` for the caller, so a bodyless POST works.", "properties": {"name": {"anyOf": [{"maxLength": 200, "minLength": 1, "type": "string"}, {"type": "null"}], "title": "Name"}}, "title": "V1CreateBranchBody", "type": "object"}, "V1CreateProjectBody": {"description": "Body for ``POST /v1/projects``. Only ``name`` is required; ``language``\nand ``intent`` default server-side to ``python``/``cli`` (mirrored by the\nCLI ``hydrate new`` defaults).", "properties": {"intent": {"anyOf": [{"maxLength": 40, "type": "string"}, {"type": "null"}], "default": "cli", "description": "What the project is for. Defaults to 'cli'.", "title": "Intent"}, "language": {"anyOf": [{"maxLength": 40, "type": "string"}, {"type": "null"}], "default": "python", "description": "Primary language. Defaults to 'python'.", "title": "Language"}, "name": {"description": "Project name. Unique per-user, case-insensitive.", "maxLength": 200, "minLength": 1, "title": "Name", "type": "string"}}, "required": ["name"], "title": "V1CreateProjectBody", "type": "object"}, "V1DeltasBody": {"description": "Request body for ``POST /v1/branches/{bid}/deltas``.", "properties": {"deltas": {"description": "Ordered list of delta operations to apply. Each must carry a `type` field in the closed write-surface vocabulary (add_node / delete_node / update_node_data / reparent_node / add_edge / delete_edge / flatten_boundary). Empty list returns 200 with `applied: false` and no version bump.", "items": {"discriminator": {"mapping": {"add_edge": "#/components/schemas/AddEdgeDelta", "add_node": "#/components/schemas/AddNodeDelta", "delete_edge": "#/components/schemas/DeleteEdgeDelta", "delete_node": "#/components/schemas/DeleteNodeDelta", "flatten_boundary": "#/components/schemas/FlattenBoundaryDelta", "reparent_node": "#/components/schemas/ReparentNodeDelta", "update_node_data": "#/components/schemas/UpdateNodeDataDelta"}, "propertyName": "type"}, "oneOf": [{"$ref": "#/components/schemas/AddNodeDelta"}, {"$ref": "#/components/schemas/DeleteNodeDelta"}, {"$ref": "#/components/schemas/UpdateNodeDataDelta"}, {"$ref": "#/components/schemas/ReparentNodeDelta"}, {"$ref": "#/components/schemas/AddEdgeDelta"}, {"$ref": "#/components/schemas/DeleteEdgeDelta"}, {"$ref": "#/components/schemas/FlattenBoundaryDelta"}]}, "title": "Deltas", "type": "array"}, "expected_version": {"description": "The branch.version the client believes is current. If it doesn't match the server's view at commit time, the route 409s with the actual current_version in the detail.", "minimum": 0.0, "title": "Expected Version", "type": "integer"}, "positions": {"anyOf": [{"additionalProperties": {"$ref": "#/components/schemas/V1NodePosition"}, "type": "object"}, {"type": "null"}], "description": "Optional explicit cell pins as `node_id -> {x, y}`, applied to the branch owner's view AFTER the deltas. **Additive**: a pin is introduced only for a node the owner hasn't already placed \u2014 it never overwrites a hand-placement and never deletes. Use only to override the derived grid placement for nodes you create.", "title": "Positions"}}, "required": ["expected_version"], "title": "V1DeltasBody", "type": "object"}, "V1NodePosition": {"description": "A single explicit pin coordinate on the `/v1/` write surface.", "properties": {"x": {"title": "X", "type": "number"}, "y": {"title": "Y", "type": "number"}}, "required": ["x", "y"], "title": "V1NodePosition", "type": "object"}, "V1PatchBranchBody": {"description": "Body for ``PATCH /v1/branches/{branch_id}``. Rename only \u2014 a branch\nhas no archive state the way a project does; discard (the DELETE route\nbeside this one) is already the non-listed-but-recoverable-by-id shape a\nproject gets from archiving. ``name`` is required (not optional-with-a-\nno-fields-guard, unlike the project patch body): there is only one field,\nso an absent body is already a 422 from Pydantic without extra code.", "properties": {"name": {"description": "New name for the branch.", "maxLength": 200, "minLength": 1, "title": "Name", "type": "string"}}, "required": ["name"], "title": "V1PatchBranchBody", "type": "object"}, "V1PatchProjectBody": {"description": "Body for ``PATCH /v1/projects/{project_id}``.\n\nDeliberately narrower than the legacy session router's patch body, which\nalso carries ``language``, ``intent`` and ``layout_mode``. Those are\ncreation-time metadata on the agent surface; widening this later is\nadditive, whereas shipping fields the CLI has no verb for is not.", "properties": {"archived": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Archive or restore. Archived projects drop out of `GET /v1/projects` but stay reachable by id.", "title": "Archived"}, "name": {"anyOf": [{"maxLength": 200, "minLength": 1, "type": "string"}, {"type": "null"}], "description": "New name. Must be unique among your active projects (case-insensitive).", "title": "Name"}}, "title": "V1PatchProjectBody", "type": "object"}, "V1ValidateBody": {"description": "Request body for ``POST /v1/branches/{bid}/validate``.", "properties": {"deltas": {"items": {"discriminator": {"mapping": {"add_edge": "#/components/schemas/AddEdgeDelta", "add_node": "#/components/schemas/AddNodeDelta", "delete_edge": "#/components/schemas/DeleteEdgeDelta", "delete_node": "#/components/schemas/DeleteNodeDelta", "flatten_boundary": "#/components/schemas/FlattenBoundaryDelta", "reparent_node": "#/components/schemas/ReparentNodeDelta", "update_node_data": "#/components/schemas/UpdateNodeDataDelta"}, "propertyName": "type"}, "oneOf": [{"$ref": "#/components/schemas/AddNodeDelta"}, {"$ref": "#/components/schemas/DeleteNodeDelta"}, {"$ref": "#/components/schemas/UpdateNodeDataDelta"}, {"$ref": "#/components/schemas/ReparentNodeDelta"}, {"$ref": "#/components/schemas/AddEdgeDelta"}, {"$ref": "#/components/schemas/DeleteEdgeDelta"}, {"$ref": "#/components/schemas/FlattenBoundaryDelta"}]}, "title": "Deltas", "type": "array"}}, "title": "V1ValidateBody", "type": "object"}, "ValidateResponse": {"additionalProperties": false, "properties": {"branch": {"$ref": "#/components/schemas/BranchRef"}, "findings": {"items": {"$ref": "#/components/schemas/Finding"}, "title": "Findings", "type": "array"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "valid": {"title": "Valid", "type": "boolean"}, "version": {"title": "Version", "type": "string"}}, "required": ["version", "project_id", "branch", "valid"], "title": "ValidateResponse", "type": "object"}, "ValidationError": {"properties": {"ctx": {"title": "Context", "type": "object"}, "input": {"title": "Input"}, "loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "title": "Location", "type": "array"}, "msg": {"title": "Message", "type": "string"}, "type": {"title": "Error Type", "type": "string"}}, "required": ["loc", "msg", "type"], "title": "ValidationError", "type": "object"}, "Verification": {"description": "A test specification attached to a behavior node.", "properties": {"author": {"enum": ["user", "agent"], "title": "Author", "type": "string"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "text": {"title": "Text", "type": "string"}, "type": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Type"}}, "required": ["id", "author", "text"], "title": "Verification", "type": "object"}, "VersionConflictBody": {"additionalProperties": false, "properties": {"current_version": {"title": "Current Version", "type": "integer"}, "error": {"const": "version_conflict", "title": "Error", "type": "string"}}, "required": ["error", "current_version"], "title": "VersionConflictBody", "type": "object"}, "WireEdge": {"additionalProperties": false, "description": "Read-path edge: `source`/`target` are server-derived NODE ids\n(absent from the client-authored `h2o.types.Edge`); `*_handle` are the\nPORT ids.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "source": {"format": "uuid", "title": "Source", "type": "string"}, "source_handle": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Source Handle"}, "target": {"format": "uuid", "title": "Target", "type": "string"}, "target_handle": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Target Handle"}}, "required": ["id", "source", "target", "source_handle", "target_handle"], "title": "WireEdge", "type": "object"}, "WireNode": {"additionalProperties": false, "properties": {"data": {"$ref": "#/components/schemas/WireNodeData"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "kind": {"enum": ["behavior", "boundary", "state", "io", "interface"], "title": "Kind", "type": "string"}, "parent_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Parent Id"}, "position": {"$ref": "#/components/schemas/Position"}}, "required": ["id", "kind", "parent_id", "position", "data"], "title": "WireNode", "type": "object"}, "WireNodeData": {"additionalProperties": false, "description": "Read-path node `data`. NOT `h2o.NodeData`: the response omits\n`kind`/`parent_id` (those live on the wrapper) and emits the boundary/\nexternal scalars matrix-conditionally \u2014 so the matrix fields are\noptional (absent = not-applicable-to-kind).", "properties": {"config": {"items": {"$ref": "#/components/schemas/WirePort"}, "title": "Config", "type": "array"}, "constraints": {"items": {"type": "string"}, "title": "Constraints", "type": "array"}, "description": {"title": "Description", "type": "string"}, "documentation_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Documentation Url"}, "external_kind": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "External Kind"}, "inputs": {"items": {"$ref": "#/components/schemas/WirePort"}, "title": "Inputs", "type": "array"}, "is_external": {"title": "Is External", "type": "boolean"}, "is_test_node": {"title": "Is Test Node", "type": "boolean"}, "language": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Language"}, "name": {"title": "Name", "type": "string"}, "outputs": {"items": {"$ref": "#/components/schemas/WirePort"}, "title": "Outputs", "type": "array"}, "path_prefix": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Path Prefix"}, "protocol": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Protocol"}, "source_decisions": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "default": null, "title": "Source Decisions"}, "status": {"title": "Status", "type": "string"}, "user_kind": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "User Kind"}, "verifications": {"items": {"$ref": "#/components/schemas/WireVerification"}, "title": "Verifications", "type": "array"}}, "required": ["name", "description", "status", "is_test_node", "is_external"], "title": "WireNodeData", "type": "object"}, "WirePort": {"additionalProperties": false, "properties": {"contract_name": {"anyOf": [{"maxLength": 500, "type": "string"}, {"type": "null"}], "default": null, "title": "Contract Name"}, "description": {"default": "", "title": "Description", "type": "string"}, "external": {"default": false, "title": "External", "type": "boolean"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"default": "", "title": "Name", "type": "string"}, "type": {"default": "", "title": "Type", "type": "string"}}, "required": ["id"], "title": "WirePort", "type": "object"}, "WireVerification": {"additionalProperties": false, "properties": {"author": {"enum": ["user", "agent"], "title": "Author", "type": "string"}, "id": {"format": "uuid", "title": "Id", "type": "string"}, "text": {"title": "Text", "type": "string"}, "type": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Type"}}, "required": ["id", "author", "text"], "title": "WireVerification", "type": "object"}}, "securitySchemes": {"Bearer": {"description": "API key. Send as `Authorization: Bearer `.", "scheme": "bearer", "type": "http"}}}, "info": {"description": "The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication).\n\n**Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`.", "title": "hydrate.sh v1 API", "version": "1.0.0-rc.1"}, "openapi": "3.1.0", "paths": {"/v1/__healthz": {"get": {"description": "Returns `{\"ok\": true}` when the /v1/ surface is serving traffic. Unauthenticated. Use this for load-balancer and uptime-monitor checks; a 404 from this path means the /v1/ surface is disabled on the responding instance, not that the server is down.", "operationId": "healthz_v1___healthz_get", "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HealthzResponse"}}}, "description": "Successful Response"}}, "security": [], "summary": "Liveness probe", "tags": ["health"]}}, "/v1/branches/{branch_id}": {"delete": {"description": "Discard a working branch you own. Its structural content (nodes, ports, edges) is removed and the branch drops out of `GET /v1/projects/{project_id}/branches`.\n\nThe branch row and its `structural_changes` audit trail are kept for provenance \u2014 this is the same `discard` the legacy session router has always done, not a hard row delete. Re-deleting an already-discarded (or already-merged) branch is refused with `409 branch_not_active` rather than silently reporting success again.\n\nRequires `expected_version` \u2014 the branch `version` you last read, same optimistic-concurrency contract as `POST .../deltas`. If the branch moved underneath you (someone else pushed a delta) the request 409s `version_conflict` with the actual `current_version` rather than silently destroying content newer than what you saw.\n\nRequires `graph:write` \u2014 deliberately NOT a scope of its own, unlike `project:delete`. See the reasoning comment above this route for the full tradeoff (reversibility, per-call cost, and listing visibility all differ from delta-erasure, but branches stay cheap/low-priority enough that a dedicated scope is deferred, not ruled out).\n\nThe protected `main` branch cannot be deleted: it returns `409 main_not_writable`, the same refusal a structural write to main gets.\n\nOnly the branch's owner may delete it. A caller who can see the branch but does not own it \u2014 or cannot see it at all \u2014 gets the same `404`, so the response never reveals whether a branch exists.", "operationId": "delete_branch_v1_branches__branch_id__delete", "parameters": [{"description": "The branch to delete.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to delete.", "format": "uuid", "title": "Branch Id", "type": "string"}}, {"description": "The branch.version the client believes is current. If it doesn't match the server's view at commit time, the route 409s `version_conflict` with the actual current_version in the detail \u2014 mirrors the deltas route's OCC contract so a stale caller can't silently destroy newer content.", "in": "query", "name": "expected_version", "required": true, "schema": {"description": "The branch.version the client believes is current. If it doesn't match the server's view at commit time, the route 409s `version_conflict` with the actual current_version in the detail \u2014 mirrors the deltas route's OCC contract so a stale caller can't silently destroy newer content.", "minimum": 0, "title": "Expected Version", "type": "integer"}}], "responses": {"204": {"description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Delete a working branch", "tags": ["branches"]}, "patch": {"description": "Rename a working branch you own. There is no separate archive state for a branch (that shape is `DELETE`, which is already recoverable by id via the audit trail if needed) \u2014 this route is rename-only.\n\nRequires `graph:write`, same as the delta-apply route: a rename round-trips (nothing is destroyed), so it carries no scope of its own.\n\nThe protected `main` branch cannot be renamed: `409 main_not_writable`. A branch that exists but is no longer `active` (already discarded or merged) cannot be renamed either: `409 branch_not_active`.\n\nOnly the branch's owner may rename it; a caller who can see the branch but does not own it gets the same `404` a non-existent branch gets.", "operationId": "patch_branch_v1_branches__branch_id__patch", "parameters": [{"description": "The branch to rename.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to rename.", "format": "uuid", "title": "Branch Id", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/V1PatchBranchBody"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BranchPatchResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Rename a working branch", "tags": ["branches"]}}, "/v1/branches/{branch_id}/boundary/{node_id}": {"get": {"description": "Returns a boundary node, its direct children, and the edges between those children \u2014 on this branch. Edges that cross the boundary belong to the parent cell's inspection and are not returned.", "operationId": "fetch_branch_boundary_v1_branches__branch_id__boundary__node_id__get", "parameters": [{"description": "The branch to read.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to read.", "format": "uuid", "title": "Branch Id", "type": "string"}}, {"description": "The boundary node to inspect.", "in": "path", "name": "node_id", "required": true, "schema": {"description": "The boundary node to inspect.", "format": "uuid", "title": "Node Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BoundaryResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Read a boundary's children and interior edges on a branch", "tags": ["branches"]}}, "/v1/branches/{branch_id}/deltas": {"post": {"description": "Apply an ordered batch of structural changes to a branch under optimistic concurrency. Pass the `version` from the branch read as `expected_version`; a 409 with `current_version` is returned if the branch moved underneath you. Pair with the `Idempotency-Key` header for at-most-once apply across retries.", "operationId": "apply_branch_deltas_v1_branches__branch_id__deltas_post", "parameters": [{"description": "The branch to apply to.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to apply to.", "format": "uuid", "title": "Branch Id", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/V1DeltasBody"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DeltaApplyResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"content": {"application/json": {"schema": {"properties": {"detail": {"oneOf": [{"properties": {"code": {"type": "string"}, "message": {"type": "string"}}, "required": ["code", "message"], "type": "object"}, {"$ref": "#/components/schemas/NotFoundBody"}]}}, "required": ["detail"], "type": "object"}}}, "description": "Branch/project not found or not accessible, OR a delta targets a node that doesn't exist."}, "409": {"content": {"application/json": {"schema": {"properties": {"detail": {"oneOf": [{"$ref": "#/components/schemas/VersionConflictBody"}, {"$ref": "#/components/schemas/BranchNotActiveBody"}]}}, "required": ["detail"], "type": "object"}}}, "description": "OCC version conflict, or the branch is not active."}, "422": {"content": {"application/json": {"schema": {"oneOf": [{"$ref": "#/components/schemas/HTTPValidationError"}, {"properties": {"detail": {"$ref": "#/components/schemas/DeltaFieldErrorBody"}}, "required": ["detail"], "type": "object"}]}}}, "description": "Request body failed validation, OR a delta is malformed / unknown / not an object."}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Apply a delta batch to a branch (OCC)", "tags": ["branches"]}}, "/v1/branches/{branch_id}/graph": {"get": {"description": "Read the full graph (nodes, ports, edges, version) of a specific branch. Use the returned `version` as `expected_version` on a subsequent apply to that branch.", "operationId": "fetch_branch_graph_v1_branches__branch_id__graph_get", "parameters": [{"description": "The branch to read.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to read.", "format": "uuid", "title": "Branch Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/GraphResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Read a specific branch's graph", "tags": ["branches"]}}, "/v1/branches/{branch_id}/node/{node_id}": {"get": {"description": "Returns a node, every node one edge away from it, and those edges \u2014 on this branch. The scoped read an agent uses to load just the context around what it is about to change.", "operationId": "fetch_branch_node_v1_branches__branch_id__node__node_id__get", "parameters": [{"description": "The branch to read.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to read.", "format": "uuid", "title": "Branch Id", "type": "string"}}, {"description": "The node to read.", "in": "path", "name": "node_id", "required": true, "schema": {"description": "The node to read.", "format": "uuid", "title": "Node Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NodeNeighborhoodResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Read a node and its 1-hop neighborhood on a specific branch", "tags": ["branches"]}}, "/v1/branches/{branch_id}/subtree/{node_id}": {"get": {"description": "Returns a node and its descendants down to `depth` levels on this branch, with the edges among them. The scoped counterpart to `GET /v1/branches/{branch_id}/graph`: bounded by `depth` rather than by the size of the branch, so reading a slice of a large project never puts the whole graph on the wire.", "operationId": "fetch_branch_subtree_v1_branches__branch_id__subtree__node_id__get", "parameters": [{"description": "The branch to read.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to read.", "format": "uuid", "title": "Branch Id", "type": "string"}}, {"description": "The node to root the subtree at.", "in": "path", "name": "node_id", "required": true, "schema": {"description": "The node to root the subtree at.", "format": "uuid", "title": "Node Id", "type": "string"}}, {"description": "How many levels of descendants to include. 1 = direct children only.", "in": "query", "name": "depth", "required": false, "schema": {"default": 1, "description": "How many levels of descendants to include. 1 = direct children only.", "maximum": 32, "minimum": 1, "title": "Depth", "type": "integer"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SubtreeResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Read a node's subtree on a specific branch, to a bounded depth", "tags": ["branches"]}}, "/v1/branches/{branch_id}/validate": {"post": {"description": "Dry-run a delta batch and get back the full coherence report over the graph that would result \u2014 never mutating the branch. Returns 200 with `{valid, findings}`: `findings` is the complete list of coherence problems (an input port with no incoming edge, a wire pinned to a missing port, or an edge whose endpoint types differ), and `valid` is true only when there are no error-severity findings. The verdict is in the body, so an incoherent graph is still a 200. A 4xx is returned only for a request that can't be processed at all (a malformed body or an unknown delta type). An empty delta list reports the branch's current coherence.", "operationId": "validate_branch_deltas_v1_branches__branch_id__validate_post", "parameters": [{"description": "The branch to validate against.", "in": "path", "name": "branch_id", "required": true, "schema": {"description": "The branch to validate against.", "format": "uuid", "title": "Branch Id", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/V1ValidateBody"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidateResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"content": {"application/json": {"schema": {"properties": {"detail": {"oneOf": [{"properties": {"code": {"type": "string"}, "message": {"type": "string"}}, "required": ["code", "message"], "type": "object"}, {"$ref": "#/components/schemas/NotFoundBody"}]}}, "required": ["detail"], "type": "object"}}}, "description": "Branch/project not found or not accessible, OR a delta targets a node that doesn't exist."}, "422": {"content": {"application/json": {"schema": {"oneOf": [{"$ref": "#/components/schemas/HTTPValidationError"}, {"properties": {"detail": {"$ref": "#/components/schemas/DeltaFieldErrorBody"}}, "required": ["detail"], "type": "object"}]}}}, "description": "Request body failed validation, OR a delta is malformed / unknown / not an object."}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Dry-run a delta batch and report coherence findings", "tags": ["branches"]}}, "/v1/decisions": {"get": {"description": "One page of a project's decisions, newest first.\n\nRequires the `decisions:read` scope, which is **not** granted by\ndefault \u2014 mint a key that asks for it. Only settled states are\nserved; `captured` and `interviewing` are mid-conversation and are\nnever returned. Results are further limited to decisions owned by an\naltitude your account belongs to \u2014 a **relevance** filter, not a\nconfidentiality boundary (altitude membership is self-service).\n\nThe `filters` block echoes what was actually applied, so an empty\npage is never ambiguous about which narrowing produced it.\n\nRefusals beyond the standard envelopes:\n\n* `403 no_altitude_membership` \u2014 your account belongs to no altitude,\n so nothing here is readable. A statement about your account, not\n about the project.\n* `422 owner_layer_not_permitted` \u2014 `owner_layer` asked outside your\n membership. The filter can only narrow.\n* `422 state_not_readable` \u2014 `state` asked for a state this surface\n does not serve.", "operationId": "list_decisions_v1_decisions_get", "parameters": [{"description": "Required. Decisions are project-scoped and the read is authorized per project.", "in": "query", "name": "project_id", "required": true, "schema": {"description": "Required. Decisions are project-scoped and the read is authorized per project.", "format": "uuid", "title": "Project Id", "type": "string"}}, {"description": "Narrow to these states. Only settled outcomes are served; anything else is refused with 422 state_not_readable.", "in": "query", "name": "state", "required": false, "schema": {"anyOf": [{"items": {"maxLength": 64, "pattern": "^[a-z][a-z0-9_]*$", "type": "string"}, "maxItems": 16, "type": "array"}, {"type": "null"}], "description": "Narrow to these states. Only settled outcomes are served; anything else is refused with 422 state_not_readable.", "title": "State"}}, {"description": "Narrow to these altitudes. Must be a subset of the calling principal's altitude membership; otherwise 422 owner_layer_not_permitted.", "in": "query", "name": "owner_layer", "required": false, "schema": {"anyOf": [{"items": {"maxLength": 64, "pattern": "^[a-z][a-z0-9_]*$", "type": "string"}, "maxItems": 16, "type": "array"}, {"type": "null"}], "description": "Narrow to these altitudes. Must be a subset of the calling principal's altitude membership; otherwise 422 owner_layer_not_permitted.", "title": "Owner Layer"}}, {"in": "query", "name": "limit", "required": false, "schema": {"default": 50, "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer"}}, {"in": "query", "name": "offset", "required": false, "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DecisionsListResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "List decisions for a project", "tags": ["decisions"]}}, "/v1/decisions/{decision_id}": {"get": {"description": "One decision by id.\n\nRequires the `decisions:read` scope. A decision you may not read \u2014\nbecause it belongs to another project, another altitude, or is still\nmid-conversation \u2014 returns the same `404` as one that does not\nexist.\n\nThe URL is flat (no project component) because a decision id is\nglobally unique. Authorization is still per project: the caller's\naltitude membership is resolved FIRST, then the row is loaded, its\nproject resolved through the membership gate, and only then the\naltitude + state filters \u2014 every one of those failures producing the\nSAME 404 a nonexistent id gets, so this route is not an oracle for\n\"that decision exists but you may not see it\".\n\n`403 no_altitude_membership` is the one refusal that is not a 404,\nand it is raised before any id is looked at, so it says nothing about\nthe id.", "operationId": "get_decision_v1_decisions__decision_id__get", "parameters": [{"in": "path", "name": "decision_id", "required": true, "schema": {"format": "uuid", "title": "Decision Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DecisionResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Read one decision", "tags": ["decisions"]}}, "/v1/graph/{project_id}": {"get": {"description": "Returns the active main-branch graph (nodes, ports, edges, version) for the project. Read-only: the main branch is merge-only. To make changes, fork a branch (`POST /v1/projects/{project_id}/branches`) and apply deltas to it.", "operationId": "fetch_graph_v1_graph__project_id__get", "parameters": [{"description": "The project whose main-branch graph to fetch.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project whose main-branch graph to fetch.", "format": "uuid", "title": "Project Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/GraphResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Get the canonical graph for a project", "tags": ["graph"]}}, "/v1/graph/{project_id}/boundary/{node_id}": {"get": {"description": "Returns the boundary view for a single node: the inputs and outputs that cross the node's enforcement perimeter. Used by clients that visualize a node's contract surface in isolation from the rest of the graph.", "operationId": "fetch_boundary_v1_graph__project_id__boundary__node_id__get", "parameters": [{"description": "The project the boundary lives in.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project the boundary lives in.", "format": "uuid", "title": "Project Id", "type": "string"}}, {"description": "The boundary node to inspect.", "in": "path", "name": "node_id", "required": true, "schema": {"description": "The boundary node to inspect.", "format": "uuid", "title": "Node Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BoundaryResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Get the boundary topology around a node", "tags": ["graph"]}}, "/v1/graph/{project_id}/node/{node_id}": {"get": {"description": "Returns a single node along with its directly connected neighbors and edges (the 1-hop topology). The node must belong to the project's active main branch.", "operationId": "fetch_node_with_neighbors_v1_graph__project_id__node__node_id__get", "parameters": [{"description": "The project the node lives in.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project the node lives in.", "format": "uuid", "title": "Project Id", "type": "string"}}, {"description": "The node to fetch.", "in": "path", "name": "node_id", "required": true, "schema": {"description": "The node to fetch.", "format": "uuid", "title": "Node Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NodeNeighborhoodResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Get a node with its 1-hop neighborhood", "tags": ["graph"]}}, "/v1/graph/{project_id}/subtree/{node_id}": {"get": {"description": "Returns a node and its descendants down to ``depth`` levels, with the edges among them. The scoped read for working inside a large project: unlike the full graph read, the response is bounded by ``depth`` rather than by the size of the branch, so a slice of a big graph never puts the whole graph on the wire.", "operationId": "fetch_subtree_v1_graph__project_id__subtree__node_id__get", "parameters": [{"description": "The project the node lives in.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project the node lives in.", "format": "uuid", "title": "Project Id", "type": "string"}}, {"description": "The node to root the subtree at.", "in": "path", "name": "node_id", "required": true, "schema": {"description": "The node to root the subtree at.", "format": "uuid", "title": "Node Id", "type": "string"}}, {"description": "How many levels of descendants to include. 1 = direct children only. Levels below the cut are omitted and ``truncated`` reports that they exist.", "in": "query", "name": "depth", "required": false, "schema": {"default": 1, "description": "How many levels of descendants to include. 1 = direct children only. Levels below the cut are omitted and ``truncated`` reports that they exist.", "maximum": 32, "minimum": 1, "title": "Depth", "type": "integer"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SubtreeResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Get a node's subtree to a bounded depth", "tags": ["graph"]}}, "/v1/projects": {"get": {"description": "Returns the projects the caller can see. For API-key callers the list is further filtered by the per-key project allowlist (if configured). Archived projects are excluded. An empty list is a normal response when the caller has no visible projects. Archived projects are excluded unless `include_archived=true`.", "operationId": "list_projects_v1_projects_get", "parameters": [{"description": "Maximum projects to return. Defaults to 100; max 200.", "in": "query", "name": "limit", "required": false, "schema": {"default": 100, "description": "Maximum projects to return. Defaults to 100; max 200.", "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer"}}, {"description": "Include archived projects. Off by default. Needed by name-addressed callers such as the CLI: an archived project that cannot be listed cannot be renamed or restored, which would make archiving a one-way door.", "in": "query", "name": "include_archived", "required": false, "schema": {"default": false, "description": "Include archived projects. Off by default. Needed by name-addressed callers such as the CLI: an archived project that cannot be listed cannot be renamed or restored, which would make archiving a one-way door.", "title": "Include Archived", "type": "boolean"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectsListResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "List your projects", "tags": ["projects"]}, "post": {"description": "Create a new project owned by the caller. Creating a project also seeds its protected `main` branch in the same transaction, so the returned `main_branch` can be forked immediately (via `POST /v1/projects/{project_id}/branches`) with no second round-trip. The name must be unique among your active projects (case-insensitive); a collision returns 409 `name_taken`. Requires the `graph:write` scope.", "operationId": "create_project_v1_projects_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/V1CreateProjectBody"}}}, "required": true}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectCreateResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"$ref": "#/components/responses/ProjectCreateConflict"}, "422": {"$ref": "#/components/responses/ProjectCreateUnprocessable"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Create a project", "tags": ["projects"]}}, "/v1/projects/{project_id}": {"delete": {"description": "Permanently delete a project you own, along with its branches, graph and stored artifacts. This cannot be undone.\n\nRequires the `project:delete` scope, which is **separate from `graph:write` and is not implied by it** \u2014 a key that can author a graph cannot erase one unless it was minted with this scope as well. Keys issued before this scope existed do not have it and must be re-minted.\n\nOnly the project's owner may delete it. A caller who can see the project but does not own it gets the same `404` a non-existent project gets, so the response never reveals whether a project exists.", "operationId": "delete_project_v1_projects__project_id__delete", "parameters": [{"description": "The project to delete.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project to delete.", "format": "uuid", "title": "Project Id", "type": "string"}}], "responses": {"204": {"description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Delete a project", "tags": ["projects"]}, "patch": {"description": "Rename a project you own, archive it, or restore it. At least one of `name` or `archived` must be present; an empty body is rejected with `422 no_fields` rather than reporting success for a request that changed nothing.\n\nArchiving is the non-destructive alternative to deletion: the project leaves `GET /v1/projects` but remains addressable by id and can be restored. It does **not** free a slot against your project cap \u2014 only deletion does.\n\nRequires `graph:write` \u2014 deliberately NOT a scope of its own, unlike `project:delete`. That scope exists because deletion is irreversible; both edits here round-trip, so an authoring key holding them cannot destroy anything it cannot also put back. The consequence is that every existing `graph:write` key gains rename and archive when this ships, with no re-mint.\n\nOnly the owner may modify a project; a caller who can see it but does not own it gets the same `404` a non-existent project gets.", "operationId": "patch_project_v1_projects__project_id__patch", "parameters": [{"description": "The project to modify.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project to modify.", "format": "uuid", "title": "Project Id", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/V1PatchProjectBody"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectPatchResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Rename or archive a project", "tags": ["projects"]}}, "/v1/projects/{project_id}/branches": {"get": {"description": "List your active working branches on this project. The protected main branch is not included. Each branch is a unit of work you can read and apply changes to before merging into main.", "operationId": "list_branches_v1_projects__project_id__branches_get", "parameters": [{"description": "The project to list branches for.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project to list branches for.", "format": "uuid", "title": "Project Id", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BranchListResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "List the caller's working branches on a project", "tags": ["branches"]}, "post": {"description": "Fork a new working branch from the project's main branch. The branch is copied from main's current state and owned by you; your changes stay isolated on it until merged. Requires an editor role, and there is a per-project limit on how many active branches you can hold at once.", "operationId": "create_branch_v1_projects__project_id__branches_post", "parameters": [{"description": "The project to fork a branch in.", "in": "path", "name": "project_id", "required": true, "schema": {"description": "The project to fork a branch in.", "format": "uuid", "title": "Project Id", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/V1CreateBranchBody"}}}, "required": true}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BranchCreateResponse"}}}, "description": "Successful Response"}, "401": {"$ref": "#/components/responses/Unauthenticated"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}, "description": "Validation Error"}, "429": {"$ref": "#/components/responses/RateLimited"}}, "summary": "Start a unit of work (fork a branch from main)", "tags": ["branches"]}}}, "security": [{"Bearer": []}], "servers": [{"description": "Production", "url": "https://api.hydrate.sh"}], "tags": [{"description": "Work on a branch (a unit of work) rather than the protected main branch: fork a branch from main, list your branches, and read / validate / apply deltas against a specific branch.", "name": "branches"}, {"description": "Read the decisions behind a project's graph. Requires the separate `decisions:read` scope, and serves only reviewed states, filtered to the caller's altitude membership.", "name": "decisions"}, {"description": "Read the canonical graph for a project, validate a delta batch (dry-run), or apply a delta batch through the OCC + idempotency middleware chain.", "name": "graph"}, {"description": "Liveness probes. Unauthenticated. Used by load balancers and uptime monitors.", "name": "health"}, {"description": "List the projects your principal can see. Filtered by membership + (for API keys) the per-key project allowlist.", "name": "projects"}]} \ No newline at end of file diff --git a/src/cmd/partition.rs b/src/cmd/partition.rs index 2f32fb5..167f041 100644 --- a/src/cmd/partition.rs +++ b/src/cmd/partition.rs @@ -39,12 +39,13 @@ use hydrate_wire::models::{self, ValidateResponse}; type Key = (String, String); fn key(f: &models::Finding) -> Key { - // The code enum has no Display; its serde spelling is the stable name. - let code = serde_json::to_value(f.code) - .ok() - .and_then(|v| v.as_str().map(str::to_string)) - .unwrap_or_else(|| format!("{:?}", f.code)); - (code, f.locator.clone()) + // `code` is a plain `String` — the server publishes it as an OPEN set, so + // there is no enum to spell out. This used to round-trip a generated enum + // through serde to recover its wire name; the round-trip is gone with the + // enum, and so is the silent failure it papered over (an unrecognized code + // could not be represented at all, so the whole response failed to + // deserialize before this function ever ran). + (f.code.clone(), f.locator.clone()) } /// Count each key, rather than collecting a set. @@ -230,9 +231,9 @@ mod tests { use super::*; use uuid::Uuid; - fn finding(code: models::finding::Code, locator: &str) -> models::Finding { + fn finding(code: &str, locator: &str) -> models::Finding { models::Finding { - code, + code: code.to_string(), locator: locator.to_string(), message: format!("about {locator}"), severity: models::finding::Severity::Error, @@ -252,8 +253,8 @@ mod tests { } } - const UNSAT: models::finding::Code = models::finding::Code::UnsatisfiedInput; - const MISMATCH: models::finding::Code = models::finding::Code::TypeMismatch; + const UNSAT: &str = "unsatisfied_input"; + const MISMATCH: &str = "type_mismatch"; #[test] fn a_finding_in_both_reports_is_inherited() { diff --git a/src/cmd/validate.rs b/src/cmd/validate.rs index dbf1dfd..34c0a73 100644 --- a/src/cmd/validate.rs +++ b/src/cmd/validate.rs @@ -222,7 +222,7 @@ fn finding_line(f: &models::Finding, locators: &Locators) -> String { format!( " [{}] {} {}: {}", severity_str(f.severity), - code_str(f.code), + f.code, locator, locators.rewrite(&f.message), ) @@ -239,7 +239,7 @@ fn bucket_json(findings: &[models::Finding], locators: &Locators) -> Vec Vec &'static str { - match code { - models::finding::Code::UnsatisfiedInput => "unsatisfied_input", - models::finding::Code::DanglingWire => "dangling_wire", - models::finding::Code::TypeMismatch => "type_mismatch", - } -} - /// The machine token for a finding's severity. fn severity_str(severity: models::finding::Severity) -> &'static str { match severity { @@ -559,7 +549,14 @@ fn plural(n: usize, noun: &str) -> String { #[cfg(test)] mod tests { use super::*; - use hydrate_wire::models::finding::{Code, Severity}; + use hydrate_wire::models::finding::Severity; + + // The server publishes `code` as an open string, so these are plain + // literals rather than generated enum variants. That is the point of + // the change: a code this build has never heard of is renderable. + const UNSAT: &str = "unsatisfied_input"; + const DANGLING: &str = "dangling_wire"; + const MISMATCH: &str = "type_mismatch"; /// A resolver that knows nothing — the pre-existing tests assert the /// raw-id rendering, which is exactly what an empty local view produces. @@ -576,9 +573,9 @@ mod tests { } } - fn finding(code: Code, severity: Severity, locator: &str, message: &str) -> models::Finding { + fn finding(code: &str, severity: Severity, locator: &str, message: &str) -> models::Finding { models::Finding { - code, + code: code.to_string(), severity, locator: locator.to_string(), message: message.to_string(), @@ -602,7 +599,7 @@ mod tests { let r = response( false, vec![finding( - Code::UnsatisfiedInput, + UNSAT, Severity::Error, &port.to_string(), &format!("input port {port} has no incoming edge"), @@ -627,7 +624,7 @@ mod tests { let r = response( false, vec![finding( - Code::UnsatisfiedInput, + UNSAT, Severity::Error, &port.to_string(), &raw_message, @@ -661,7 +658,7 @@ mod tests { let r = response( false, vec![finding( - Code::UnsatisfiedInput, + UNSAT, Severity::Error, &port.to_string(), "input port has no incoming edge", @@ -682,7 +679,7 @@ mod tests { let r = response( false, vec![finding( - Code::UnsatisfiedInput, + UNSAT, Severity::Error, &Uuid::from_u128(80).to_string(), "unfed", @@ -700,12 +697,7 @@ mod tests { let port = Uuid::from_u128(81); let r = response( false, - vec![finding( - Code::UnsatisfiedInput, - Severity::Error, - &port.to_string(), - "unfed", - )], + vec![finding(UNSAT, Severity::Error, &port.to_string(), "unfed")], ); assert!(resolution_notes(&r, &locators_knowing(port, "Api:in:k"), None).is_empty()); } @@ -747,7 +739,7 @@ mod tests { let r = response( false, vec![finding( - Code::UnsatisfiedInput, + UNSAT, Severity::Error, "node-1", "input port 'raw' has no incoming edge", @@ -766,7 +758,7 @@ mod tests { let r = response( false, vec![finding( - Code::TypeMismatch, + MISMATCH, Severity::Error, "edge-9", "endpoint types differ: HotDog vs Rating", @@ -800,12 +792,7 @@ mod tests { // only error-severity findings gate the exit code. let r = response( true, - vec![finding( - Code::UnsatisfiedInput, - Severity::Warning, - "node-7", - "advisory only", - )], + vec![finding(UNSAT, Severity::Warning, "node-7", "advisory only")], ); assert_eq!(exit_code(&r), exit::SUCCESS); let human = render(&r, &binding(), &no_locators(), OutputMode::Human); @@ -818,14 +805,9 @@ mod tests { let r = response( false, vec![ + finding(UNSAT, Severity::Warning, "node-7", "advisory"), finding( - Code::UnsatisfiedInput, - Severity::Warning, - "node-7", - "advisory", - ), - finding( - Code::DanglingWire, + DANGLING, Severity::Error, "edge-2", "wire to a missing port", @@ -864,7 +846,7 @@ mod tests { let r = response( false, vec![finding( - Code::UnsatisfiedInput, + UNSAT, Severity::Error, &port.to_string(), &format!("input port {port} has no incoming edge"), @@ -938,12 +920,7 @@ mod tests { // the local error-severity scan, governs. Exit VALIDATION, read "Invalid". let r = response( false, - vec![finding( - Code::UnsatisfiedInput, - Severity::Warning, - "node-7", - "advisory only", - )], + vec![finding(UNSAT, Severity::Warning, "node-7", "advisory only")], ); assert_eq!(exit_code(&r), exit::VALIDATION); let human = render(&r, &binding(), &no_locators(), OutputMode::Human); @@ -967,10 +944,7 @@ mod tests { assert!(disagreement_warning(&r).is_some()); // valid:true but an error-severity finding present → disagreement. - let r = response( - true, - vec![finding(Code::DanglingWire, Severity::Error, "e", "m")], - ); + let r = response(true, vec![finding(DANGLING, Severity::Error, "e", "m")]); assert!(disagreement_warning(&r).is_some()); } @@ -979,10 +953,7 @@ mod tests { // valid:true + no error findings → agree. assert!(disagreement_warning(&response(true, vec![])).is_none()); // valid:false + an error finding → agree. - let r = response( - false, - vec![finding(Code::DanglingWire, Severity::Error, "e", "m")], - ); + let r = response(false, vec![finding(DANGLING, Severity::Error, "e", "m")]); assert!(disagreement_warning(&r).is_none()); } @@ -993,12 +964,7 @@ mod tests { // disagree with the exit status beside it — on a dirty branch, literally // `valid:false` with exit 0. let port = Uuid::from_u128(55); - let inherited = finding( - Code::UnsatisfiedInput, - Severity::Error, - &port.to_string(), - "inherited", - ); + let inherited = finding(UNSAT, Severity::Error, &port.to_string(), "inherited"); let whole = response(false, vec![inherited.clone()]); let p = Partition { introduced: vec![], @@ -1039,14 +1005,9 @@ mod tests { #[test] fn partitioned_human_report_separates_the_buckets() { let port = Uuid::from_u128(56); - let mine = finding( - Code::UnsatisfiedInput, - Severity::Error, - &port.to_string(), - "mine", - ); + let mine = finding(UNSAT, Severity::Error, &port.to_string(), "mine"); let theirs = finding( - Code::TypeMismatch, + MISMATCH, Severity::Error, &Uuid::from_u128(57).to_string(), "theirs", @@ -1077,7 +1038,7 @@ mod tests { let inherited: Vec = (0..99) .map(|i| { finding( - Code::UnsatisfiedInput, + UNSAT, Severity::Error, &Uuid::from_u128(1000 + i).to_string(), "inherited", @@ -1125,12 +1086,7 @@ mod tests { // a non-zero exit and now gets 0. Same command, different answer, so it // must leave a runtime trace — a release note is not one. let port = Uuid::from_u128(71); - let f = finding( - Code::UnsatisfiedInput, - Severity::Error, - &port.to_string(), - "inherited", - ); + let f = finding(UNSAT, Severity::Error, &port.to_string(), "inherited"); let p = Partition { introduced: vec![], inherited: vec![f.clone()], @@ -1161,3 +1117,63 @@ mod tests { assert!(body.deltas.as_ref().unwrap().is_empty()); } } + +#[cfg(test)] +mod open_code_tests { + use super::*; + use hydrate_wire::models::finding::Severity; + + /// The whole point of the server publishing `code` as an open string. + /// + /// The generated client used to close it into an enum with three variants + /// and no fallback arm, so a validate response containing a code this + /// build had never heard of failed to deserialize *entirely* — not the one + /// unfamiliar finding, the whole response. Every additive server-side rule + /// was therefore a coordinated release, and an installed CLI that was + /// merely out of date became one that could not read a validate response + /// at all. + #[test] + fn a_finding_code_this_build_has_never_heard_of_deserializes() { + let raw = serde_json::json!({ + "code": "orphan_node", + "severity": "warning", + "locator": "a3f", + "message": "this node carries no source decision", + }); + let f: models::Finding = + serde_json::from_value(raw).expect("an unknown code must not break the response"); + assert_eq!(f.code, "orphan_node"); + assert_eq!(f.severity, Severity::Warning); + } + + /// …and it must reach the human output verbatim rather than being dropped + /// or rendered as a placeholder. A finding the CLI cannot name is still a + /// finding the user has to act on. + #[test] + fn an_unknown_code_is_rendered_verbatim() { + let f = models::Finding { + code: "orphan_node".to_string(), + severity: Severity::Warning, + locator: "a3f".to_string(), + message: "no source decision".to_string(), + }; + let line = finding_line(&f, &Locators::new(None, &crate::state::Stage::empty())); + assert!(line.contains("orphan_node"), "{line}"); + assert!(line.contains("no source decision"), "{line}"); + } + + /// `severity` stays CLOSED on purpose — it is a two-valued verdict the CLI + /// branches on to pick an exit code, so a third value is a real breaking + /// change that deserves a coordinated release rather than a silent + /// fall-through to "not an error". + #[test] + fn severity_stays_closed() { + let raw = serde_json::json!({ + "code": "orphan_node", + "severity": "advisory", + "locator": "a3f", + "message": "m", + }); + assert!(serde_json::from_value::(raw).is_err()); + } +} diff --git a/wire/.openapi-generator/FILES b/wire/.openapi-generator/FILES index 6d5af78..796e3a8 100644 --- a/wire/.openapi-generator/FILES +++ b/wire/.openapi-generator/FILES @@ -1,6 +1,7 @@ Cargo.toml src/apis/branches_api.rs src/apis/configuration.rs +src/apis/decisions_api.rs src/apis/graph_api.rs src/apis/health_api.rs src/apis/mod.rs @@ -8,6 +9,8 @@ src/apis/projects_api.rs src/lib.rs src/models/add_edge_delta.rs src/models/add_node_delta.rs +src/models/anchor_ref.rs +src/models/applied_decision_filters.rs src/models/apply_branch_deltas_v1_branches__branch_id__deltas_post_404_response.rs src/models/apply_branch_deltas_v1_branches__branch_id__deltas_post_404_response_detail.rs src/models/apply_branch_deltas_v1_branches__branch_id__deltas_post_404_response_detail_one_of.rs @@ -20,7 +23,11 @@ src/models/branch_create_response.rs src/models/branch_list_response.rs src/models/branch_meta.rs src/models/branch_not_active_body.rs +src/models/branch_patch_response.rs src/models/branch_ref.rs +src/models/decision_out.rs +src/models/decision_response.rs +src/models/decisions_list_response.rs src/models/delete_edge_delta.rs src/models/delete_node_delta.rs src/models/delta_apply_response.rs @@ -39,16 +46,12 @@ src/models/inline_object_2_detail.rs src/models/inline_object_3.rs src/models/inline_object_3_detail.rs src/models/inline_object_4.rs -src/models/inline_object_4_detail.rs src/models/inline_object_5.rs -src/models/inline_object_5_detail.rs src/models/inline_object_6.rs +src/models/inline_object_6_detail.rs +src/models/inline_object_6_detail_one_of.rs src/models/inline_object_7.rs -src/models/inline_object_8.rs -src/models/inline_object_8_detail.rs -src/models/inline_object_8_detail_one_of.rs -src/models/inline_object_9.rs -src/models/inline_object_9_detail.rs +src/models/inline_object_7_detail.rs src/models/inline_object_detail.rs src/models/main_branch_ref.rs src/models/mod.rs @@ -71,6 +74,7 @@ src/models/v1_create_project_body.rs src/models/v1_deltas_body.rs src/models/v1_deltas_body_deltas_inner.rs src/models/v1_node_position.rs +src/models/v1_patch_branch_body.rs src/models/v1_patch_project_body.rs src/models/v1_validate_body.rs src/models/validate_response.rs diff --git a/wire/src/apis/branches_api.rs b/wire/src/apis/branches_api.rs index a355672..d1c5049 100644 --- a/wire/src/apis/branches_api.rs +++ b/wire/src/apis/branches_api.rs @@ -30,6 +30,15 @@ pub struct CreateBranchV1ProjectsProjectIdBranchesPostParams { pub v1_create_branch_body: models::V1CreateBranchBody } +/// struct for passing parameters to the method [`delete_branch_v1_branches_branch_id_delete`] +#[derive(Clone, Debug)] +pub struct DeleteBranchV1BranchesBranchIdDeleteParams { + /// The branch to delete. + pub branch_id: String, + /// The branch.version the client believes is current. If it doesn't match the server's view at commit time, the route 409s `version_conflict` with the actual current_version in the detail — mirrors the deltas route's OCC contract so a stale caller can't silently destroy newer content. + pub expected_version: u32 +} + /// struct for passing parameters to the method [`fetch_branch_boundary_v1_branches_branch_id_boundary_node_id_get`] #[derive(Clone, Debug)] pub struct FetchBranchBoundaryV1BranchesBranchIdBoundaryNodeIdGetParams { @@ -73,6 +82,14 @@ pub struct ListBranchesV1ProjectsProjectIdBranchesGetParams { pub project_id: String } +/// struct for passing parameters to the method [`patch_branch_v1_branches_branch_id_patch`] +#[derive(Clone, Debug)] +pub struct PatchBranchV1BranchesBranchIdPatchParams { + /// The branch to rename. + pub branch_id: String, + pub v1_patch_branch_body: models::V1PatchBranchBody +} + /// struct for passing parameters to the method [`validate_branch_deltas_v1_branches_branch_id_validate_post`] #[derive(Clone, Debug)] pub struct ValidateBranchDeltasV1BranchesBranchIdValidatePostParams { @@ -86,12 +103,12 @@ pub struct ValidateBranchDeltasV1BranchesBranchIdValidatePostParams { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum ApplyBranchDeltasV1BranchesBranchIdDeltasPostError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::ApplyBranchDeltasV1BranchesBranchIdDeltasPost404Response), Status409(models::ApplyBranchDeltasV1BranchesBranchIdDeltasPost409Response), Status422(models::ApplyBranchDeltasV1BranchesBranchIdDeltasPost422Response), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -99,11 +116,23 @@ pub enum ApplyBranchDeltasV1BranchesBranchIdDeltasPostError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum CreateBranchV1ProjectsProjectIdBranchesPostError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`delete_branch_v1_branches_branch_id_delete`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DeleteBranchV1BranchesBranchIdDeleteError { + Status401(models::InlineObject5), + Status403(models::InlineObject), + Status404(models::InlineObject1), + Status422(models::HttpValidationError), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -111,11 +140,11 @@ pub enum CreateBranchV1ProjectsProjectIdBranchesPostError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchBranchBoundaryV1BranchesBranchIdBoundaryNodeIdGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -123,11 +152,11 @@ pub enum FetchBranchBoundaryV1BranchesBranchIdBoundaryNodeIdGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchBranchGraphV1BranchesBranchIdGraphGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -135,11 +164,11 @@ pub enum FetchBranchGraphV1BranchesBranchIdGraphGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchBranchNodeV1BranchesBranchIdNodeNodeIdGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -147,11 +176,11 @@ pub enum FetchBranchNodeV1BranchesBranchIdNodeNodeIdGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchBranchSubtreeV1BranchesBranchIdSubtreeNodeIdGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -159,11 +188,23 @@ pub enum FetchBranchSubtreeV1BranchesBranchIdSubtreeNodeIdGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum ListBranchesV1ProjectsProjectIdBranchesGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), + Status403(models::InlineObject), + Status404(models::InlineObject1), + Status422(models::HttpValidationError), + Status429(models::InlineObject4), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`patch_branch_v1_branches_branch_id_patch`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PatchBranchV1BranchesBranchIdPatchError { + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -171,11 +212,11 @@ pub enum ListBranchesV1ProjectsProjectIdBranchesGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum ValidateBranchDeltasV1BranchesBranchIdValidatePostError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::ApplyBranchDeltasV1BranchesBranchIdDeltasPost404Response), Status422(models::ApplyBranchDeltasV1BranchesBranchIdDeltasPost422Response), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -258,6 +299,34 @@ pub async fn create_branch_v1_projects_project_id_branches_post(configuration: & } } +/// Discard a working branch you own. Its structural content (nodes, ports, edges) is removed and the branch drops out of `GET /v1/projects/{project_id}/branches`. The branch row and its `structural_changes` audit trail are kept for provenance — this is the same `discard` the legacy session router has always done, not a hard row delete. Re-deleting an already-discarded (or already-merged) branch is refused with `409 branch_not_active` rather than silently reporting success again. Requires `expected_version` — the branch `version` you last read, same optimistic-concurrency contract as `POST .../deltas`. If the branch moved underneath you (someone else pushed a delta) the request 409s `version_conflict` with the actual `current_version` rather than silently destroying content newer than what you saw. Requires `graph:write` — deliberately NOT a scope of its own, unlike `project:delete`. See the reasoning comment above this route for the full tradeoff (reversibility, per-call cost, and listing visibility all differ from delta-erasure, but branches stay cheap/low-priority enough that a dedicated scope is deferred, not ruled out). The protected `main` branch cannot be deleted: it returns `409 main_not_writable`, the same refusal a structural write to main gets. Only the branch's owner may delete it. A caller who can see the branch but does not own it — or cannot see it at all — gets the same `404`, so the response never reveals whether a branch exists. +pub async fn delete_branch_v1_branches_branch_id_delete(configuration: &configuration::Configuration, params: DeleteBranchV1BranchesBranchIdDeleteParams) -> Result<(), Error> { + + let uri_str = format!("{}/v1/branches/{branch_id}", configuration.base_path, branch_id=crate::apis::urlencode(params.branch_id)); + let mut req_builder = configuration.client.request(reqwest::Method::DELETE, &uri_str); + + req_builder = req_builder.query(&[("expected_version", ¶ms.expected_version.to_string())]); + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + + if !status.is_client_error() && !status.is_server_error() { + Ok(()) + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { status, content, entity })) + } +} + /// Returns a boundary node, its direct children, and the edges between those children — on this branch. Edges that cross the boundary belong to the parent cell's inspection and are not returned. pub async fn fetch_branch_boundary_v1_branches_branch_id_boundary_node_id_get(configuration: &configuration::Configuration, params: FetchBranchBoundaryV1BranchesBranchIdBoundaryNodeIdGetParams) -> Result> { @@ -451,6 +520,45 @@ pub async fn list_branches_v1_projects_project_id_branches_get(configuration: &c } } +/// Rename a working branch you own. There is no separate archive state for a branch (that shape is `DELETE`, which is already recoverable by id via the audit trail if needed) — this route is rename-only. Requires `graph:write`, same as the delta-apply route: a rename round-trips (nothing is destroyed), so it carries no scope of its own. The protected `main` branch cannot be renamed: `409 main_not_writable`. A branch that exists but is no longer `active` (already discarded or merged) cannot be renamed either: `409 branch_not_active`. Only the branch's owner may rename it; a caller who can see the branch but does not own it gets the same `404` a non-existent branch gets. +pub async fn patch_branch_v1_branches_branch_id_patch(configuration: &configuration::Configuration, params: PatchBranchV1BranchesBranchIdPatchParams) -> Result> { + + let uri_str = format!("{}/v1/branches/{branch_id}", configuration.base_path, branch_id=crate::apis::urlencode(params.branch_id)); + let mut req_builder = configuration.client.request(reqwest::Method::PATCH, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + req_builder = req_builder.json(¶ms.v1_patch_branch_body); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BranchPatchResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::BranchPatchResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { status, content, entity })) + } +} + /// Dry-run a delta batch and get back the full coherence report over the graph that would result — never mutating the branch. Returns 200 with `{valid, findings}`: `findings` is the complete list of coherence problems (an input port with no incoming edge, a wire pinned to a missing port, or an edge whose endpoint types differ), and `valid` is true only when there are no error-severity findings. The verdict is in the body, so an incoherent graph is still a 200. A 4xx is returned only for a request that can't be processed at all (a malformed body or an unknown delta type). An empty delta list reports the branch's current coherence. pub async fn validate_branch_deltas_v1_branches_branch_id_validate_post(configuration: &configuration::Configuration, params: ValidateBranchDeltasV1BranchesBranchIdValidatePostParams) -> Result> { diff --git a/wire/src/apis/decisions_api.rs b/wire/src/apis/decisions_api.rs new file mode 100644 index 0000000..3c15d93 --- /dev/null +++ b/wire/src/apis/decisions_api.rs @@ -0,0 +1,156 @@ +/* + * hydrate.sh v1 API + * + * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. + * + * The version of the OpenAPI document: 1.0.0-rc.1 + * + * Generated by: https://openapi-generator.tech + */ + + +use reqwest; +use serde::{Deserialize, Serialize, de::Error as _}; +use crate::{apis::ResponseContent, models}; +use super::{Error, configuration, ContentType}; + +/// struct for passing parameters to the method [`get_decision_v1_decisions_decision_id_get`] +#[derive(Clone, Debug)] +pub struct GetDecisionV1DecisionsDecisionIdGetParams { + pub decision_id: String +} + +/// struct for passing parameters to the method [`list_decisions_v1_decisions_get`] +#[derive(Clone, Debug)] +pub struct ListDecisionsV1DecisionsGetParams { + /// Required. Decisions are project-scoped and the read is authorized per project. + pub project_id: String, + /// Narrow to these states. Only settled outcomes are served; anything else is refused with 422 state_not_readable. + pub state: Option>, + /// Narrow to these altitudes. Must be a subset of the calling principal's altitude membership; otherwise 422 owner_layer_not_permitted. + pub owner_layer: Option>, + pub limit: Option, + pub offset: Option +} + + +/// struct for typed errors of method [`get_decision_v1_decisions_decision_id_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetDecisionV1DecisionsDecisionIdGetError { + Status401(models::InlineObject5), + Status403(models::InlineObject), + Status404(models::InlineObject1), + Status422(models::HttpValidationError), + Status429(models::InlineObject4), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`list_decisions_v1_decisions_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ListDecisionsV1DecisionsGetError { + Status401(models::InlineObject5), + Status403(models::InlineObject), + Status404(models::InlineObject1), + Status422(models::HttpValidationError), + Status429(models::InlineObject4), + UnknownValue(serde_json::Value), +} + + +/// One decision by id. Requires the `decisions:read` scope. A decision you may not read — because it belongs to another project, another altitude, or is still mid-conversation — returns the same `404` as one that does not exist. The URL is flat (no project component) because a decision id is globally unique. Authorization is still per project: the caller's altitude membership is resolved FIRST, then the row is loaded, its project resolved through the membership gate, and only then the altitude + state filters — every one of those failures producing the SAME 404 a nonexistent id gets, so this route is not an oracle for \"that decision exists but you may not see it\". `403 no_altitude_membership` is the one refusal that is not a 404, and it is raised before any id is looked at, so it says nothing about the id. +pub async fn get_decision_v1_decisions_decision_id_get(configuration: &configuration::Configuration, params: GetDecisionV1DecisionsDecisionIdGetParams) -> Result> { + + let uri_str = format!("{}/v1/decisions/{decision_id}", configuration.base_path, decision_id=crate::apis::urlencode(params.decision_id)); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::DecisionResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::DecisionResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { status, content, entity })) + } +} + +/// One page of a project's decisions, newest first. Requires the `decisions:read` scope, which is **not** granted by default — mint a key that asks for it. Only settled states are served; `captured` and `interviewing` are mid-conversation and are never returned. Results are further limited to decisions owned by an altitude your account belongs to — a **relevance** filter, not a confidentiality boundary (altitude membership is self-service). The `filters` block echoes what was actually applied, so an empty page is never ambiguous about which narrowing produced it. Refusals beyond the standard envelopes: * `403 no_altitude_membership` — your account belongs to no altitude, so nothing here is readable. A statement about your account, not about the project. * `422 owner_layer_not_permitted` — `owner_layer` asked outside your membership. The filter can only narrow. * `422 state_not_readable` — `state` asked for a state this surface does not serve. +pub async fn list_decisions_v1_decisions_get(configuration: &configuration::Configuration, params: ListDecisionsV1DecisionsGetParams) -> Result> { + + let uri_str = format!("{}/v1/decisions", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + req_builder = req_builder.query(&[("project_id", ¶ms.project_id.to_string())]); + if let Some(ref param_value) = params.state { + req_builder = match "multi" { + "multi" => req_builder.query(¶m_value.into_iter().map(|p| ("state".to_owned(), p.to_string())).collect::>()), + _ => req_builder.query(&[("state", ¶m_value.into_iter().map(|p| p.to_string()).collect::>().join(",").to_string())]), + }; + } + if let Some(ref param_value) = params.owner_layer { + req_builder = match "multi" { + "multi" => req_builder.query(¶m_value.into_iter().map(|p| ("owner_layer".to_owned(), p.to_string())).collect::>()), + _ => req_builder.query(&[("owner_layer", ¶m_value.into_iter().map(|p| p.to_string()).collect::>().join(",").to_string())]), + }; + } + if let Some(ref param_value) = params.limit { + req_builder = req_builder.query(&[("limit", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.offset { + req_builder = req_builder.query(&[("offset", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::DecisionsListResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::DecisionsListResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { status, content, entity })) + } +} + diff --git a/wire/src/apis/graph_api.rs b/wire/src/apis/graph_api.rs index 2272e9e..14ec635 100644 --- a/wire/src/apis/graph_api.rs +++ b/wire/src/apis/graph_api.rs @@ -55,11 +55,11 @@ pub struct FetchSubtreeV1GraphProjectIdSubtreeNodeIdGetParams { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchBoundaryV1GraphProjectIdBoundaryNodeIdGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -67,11 +67,11 @@ pub enum FetchBoundaryV1GraphProjectIdBoundaryNodeIdGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchGraphV1GraphProjectIdGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -79,11 +79,11 @@ pub enum FetchGraphV1GraphProjectIdGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchNodeWithNeighborsV1GraphProjectIdNodeNodeIdGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -91,11 +91,11 @@ pub enum FetchNodeWithNeighborsV1GraphProjectIdNodeNodeIdGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum FetchSubtreeV1GraphProjectIdSubtreeNodeIdGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } diff --git a/wire/src/apis/mod.rs b/wire/src/apis/mod.rs index 0e6f5b5..75fbf03 100644 --- a/wire/src/apis/mod.rs +++ b/wire/src/apis/mod.rs @@ -112,6 +112,7 @@ impl From<&str> for ContentType { } pub mod branches_api; +pub mod decisions_api; pub mod graph_api; pub mod health_api; pub mod projects_api; diff --git a/wire/src/apis/projects_api.rs b/wire/src/apis/projects_api.rs index f56acd4..cf4ee9b 100644 --- a/wire/src/apis/projects_api.rs +++ b/wire/src/apis/projects_api.rs @@ -49,11 +49,11 @@ pub struct PatchProjectV1ProjectsProjectIdPatchParams { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum CreateProjectV1ProjectsPostError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status409(models::InlineObject2), Status422(models::InlineObject3), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -61,11 +61,11 @@ pub enum CreateProjectV1ProjectsPostError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum DeleteProjectV1ProjectsProjectIdDeleteError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -73,10 +73,10 @@ pub enum DeleteProjectV1ProjectsProjectIdDeleteError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum ListProjectsV1ProjectsGetError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status422(models::HttpValidationError), - Status429(models::InlineObject6), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } @@ -84,12 +84,11 @@ pub enum ListProjectsV1ProjectsGetError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum PatchProjectV1ProjectsProjectIdPatchError { - Status401(models::InlineObject7), + Status401(models::InlineObject5), Status403(models::InlineObject), Status404(models::InlineObject1), - Status409(models::InlineObject4), - Status422(models::InlineObject5), - Status429(models::InlineObject6), + Status422(models::HttpValidationError), + Status429(models::InlineObject4), UnknownValue(serde_json::Value), } diff --git a/wire/src/models/anchor_ref.rs b/wire/src/models/anchor_ref.rs new file mode 100644 index 0000000..03be315 --- /dev/null +++ b/wire/src/models/anchor_ref.rs @@ -0,0 +1,33 @@ +/* + * hydrate.sh v1 API + * + * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. + * + * The version of the OpenAPI document: 1.0.0-rc.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// AnchorRef : Where a decision is anchored. ``id`` is a free-text reference whose meaning depends on ``type`` (a node/boundary UUID, an error payload key, or absent for a project-level decision). ``type`` is an OPEN set, published as a plain string, for exactly the reason ``Finding.code`` is — see that model. The anchor vocabulary is server-owned and grows additively as new things become anchorable, so a consumer must tolerate a ``type`` it does not recognize. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct AnchorRef { + #[serde(rename = "id", deserialize_with = "Option::deserialize")] + pub id: Option, + /// What the decision is anchored to. Known values: `boundary`, `node`, `error_payload`, `project`. **This set is open and grows additively** — treat an unrecognized type as an opaque string and render it; do not fail on it. + #[serde(rename = "type")] + pub r#type: String, +} + +impl AnchorRef { + /// Where a decision is anchored. ``id`` is a free-text reference whose meaning depends on ``type`` (a node/boundary UUID, an error payload key, or absent for a project-level decision). ``type`` is an OPEN set, published as a plain string, for exactly the reason ``Finding.code`` is — see that model. The anchor vocabulary is server-owned and grows additively as new things become anchorable, so a consumer must tolerate a ``type`` it does not recognize. + pub fn new(id: Option, r#type: String) -> AnchorRef { + AnchorRef { + id, + r#type, + } + } +} + diff --git a/wire/src/models/applied_decision_filters.rs b/wire/src/models/applied_decision_filters.rs new file mode 100644 index 0000000..a089be6 --- /dev/null +++ b/wire/src/models/applied_decision_filters.rs @@ -0,0 +1,32 @@ +/* + * hydrate.sh v1 API + * + * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. + * + * The version of the OpenAPI document: 1.0.0-rc.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// AppliedDecisionFilters : The narrowing the server ACTUALLY applied, echoed back. Not a copy of the query string: ``owner_layer`` omitted means \"your whole altitude membership\", ``state`` omitted means \"every settled state\", and neither is knowable client-side. Without the echo an empty page is indistinguishable from \"this project has no decisions\", which is a different fact and sends the caller looking in the wrong place. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct AppliedDecisionFilters { + #[serde(rename = "owner_layer")] + pub owner_layer: Vec, + #[serde(rename = "state")] + pub state: Vec, +} + +impl AppliedDecisionFilters { + /// The narrowing the server ACTUALLY applied, echoed back. Not a copy of the query string: ``owner_layer`` omitted means \"your whole altitude membership\", ``state`` omitted means \"every settled state\", and neither is knowable client-side. Without the echo an empty page is indistinguishable from \"this project has no decisions\", which is a different fact and sends the caller looking in the wrong place. + pub fn new(owner_layer: Vec, state: Vec) -> AppliedDecisionFilters { + AppliedDecisionFilters { + owner_layer, + state, + } + } +} + diff --git a/wire/src/models/branch_patch_response.rs b/wire/src/models/branch_patch_response.rs new file mode 100644 index 0000000..dd3b5ec --- /dev/null +++ b/wire/src/models/branch_patch_response.rs @@ -0,0 +1,35 @@ +/* + * hydrate.sh v1 API + * + * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. + * + * The version of the OpenAPI document: 1.0.0-rc.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// BranchPatchResponse : Rename returns the branch alone — the project is not affected. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BranchPatchResponse { + #[serde(rename = "branch")] + pub branch: Box, + #[serde(rename = "project_id")] + pub project_id: uuid::Uuid, + #[serde(rename = "version")] + pub version: String, +} + +impl BranchPatchResponse { + /// Rename returns the branch alone — the project is not affected. + pub fn new(branch: models::BranchMeta, project_id: uuid::Uuid, version: String) -> BranchPatchResponse { + BranchPatchResponse { + branch: Box::new(branch), + project_id, + version, + } + } +} + diff --git a/wire/src/models/decision_out.rs b/wire/src/models/decision_out.rs new file mode 100644 index 0000000..dee7870 --- /dev/null +++ b/wire/src/models/decision_out.rs @@ -0,0 +1,62 @@ +/* + * hydrate.sh v1 API + * + * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. + * + * The version of the OpenAPI document: 1.0.0-rc.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// DecisionOut : A decision as the /v1 surface serves it. **Deliberately narrower than the in-app serializer.** The fields it omits are omitted for a reason, and the omission is the mitigation — adding one back is a disclosure decision, not a serializer tweak: * ``history`` — the append-only audit trail, which names the actor of every transition. Accountability data for humans in the app, not context for an agent. * ``assignee_user_id`` — a user id, same reasoning. * ``draft_ids`` / ``originating_interview_ref`` — pointers into interview transcripts, which are the candid text itself. * ``interview_session`` — the transcript. Never. * ``snoozed_until`` — in-app queue mechanics with no meaning to an external caller. * ``flag_reason`` — why a human flagged the row for approval. An in-app queue annotation about the *review*, not about the decision, and it names no behaviour an agent can act on. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct DecisionOut { + #[serde(rename = "anchor_ref")] + pub anchor_ref: Box, + #[serde(rename = "annotations", deserialize_with = "Option::deserialize")] + pub annotations: Option, + #[serde(rename = "capture_text")] + pub capture_text: String, + #[serde(rename = "captured_at")] + pub captured_at: String, + #[serde(rename = "check", deserialize_with = "Option::deserialize")] + pub check: Option, + #[serde(rename = "id")] + pub id: uuid::Uuid, + #[serde(rename = "owner_layer")] + pub owner_layer: String, + #[serde(rename = "parent_decision_id", deserialize_with = "Option::deserialize")] + pub parent_decision_id: Option, + #[serde(rename = "project_id")] + pub project_id: uuid::Uuid, + #[serde(rename = "resulting_node_ids")] + pub resulting_node_ids: Vec, + #[serde(rename = "source_layer")] + pub source_layer: String, + #[serde(rename = "state")] + pub state: String, +} + +impl DecisionOut { + /// A decision as the /v1 surface serves it. **Deliberately narrower than the in-app serializer.** The fields it omits are omitted for a reason, and the omission is the mitigation — adding one back is a disclosure decision, not a serializer tweak: * ``history`` — the append-only audit trail, which names the actor of every transition. Accountability data for humans in the app, not context for an agent. * ``assignee_user_id`` — a user id, same reasoning. * ``draft_ids`` / ``originating_interview_ref`` — pointers into interview transcripts, which are the candid text itself. * ``interview_session`` — the transcript. Never. * ``snoozed_until`` — in-app queue mechanics with no meaning to an external caller. * ``flag_reason`` — why a human flagged the row for approval. An in-app queue annotation about the *review*, not about the decision, and it names no behaviour an agent can act on. + pub fn new(anchor_ref: models::AnchorRef, annotations: Option, capture_text: String, captured_at: String, check: Option, id: uuid::Uuid, owner_layer: String, parent_decision_id: Option, project_id: uuid::Uuid, resulting_node_ids: Vec, source_layer: String, state: String) -> DecisionOut { + DecisionOut { + anchor_ref: Box::new(anchor_ref), + annotations, + capture_text, + captured_at, + check, + id, + owner_layer, + parent_decision_id, + project_id, + resulting_node_ids, + source_layer, + state, + } + } +} + diff --git a/wire/src/models/inline_object_8.rs b/wire/src/models/decision_response.rs similarity index 50% rename from wire/src/models/inline_object_8.rs rename to wire/src/models/decision_response.rs index 89d11b3..c6a3cae 100644 --- a/wire/src/models/inline_object_8.rs +++ b/wire/src/models/decision_response.rs @@ -12,15 +12,21 @@ use crate::models; use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct InlineObject8 { - #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] - pub detail: Option>, +pub struct DecisionResponse { + #[serde(rename = "decision")] + pub decision: Box, + #[serde(rename = "project_id")] + pub project_id: uuid::Uuid, + #[serde(rename = "version")] + pub version: String, } -impl InlineObject8 { - pub fn new() -> InlineObject8 { - InlineObject8 { - detail: None, +impl DecisionResponse { + pub fn new(decision: models::DecisionOut, project_id: uuid::Uuid, version: String) -> DecisionResponse { + DecisionResponse { + decision: Box::new(decision), + project_id, + version, } } } diff --git a/wire/src/models/decisions_list_response.rs b/wire/src/models/decisions_list_response.rs new file mode 100644 index 0000000..7487820 --- /dev/null +++ b/wire/src/models/decisions_list_response.rs @@ -0,0 +1,47 @@ +/* + * hydrate.sh v1 API + * + * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. + * + * The version of the OpenAPI document: 1.0.0-rc.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// DecisionsListResponse : One page of decisions for one project. ``has_more`` is explicit rather than inferred from ``len(decisions) == limit``: a page that happens to end exactly on the boundary is indistinguishable from a full one, and a caller that guesses wrong either stops early or pages forever. It is ADVISORY. The count and the page are two statements under read committed, so a decision captured between them can make ``has_more`` a row stale. Page until a page comes back short or empty rather than trusting it as an invariant — and expect the ordering (newest first) to shift a row onto a page you have already read if the ledger is being written while you walk it. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct DecisionsListResponse { + #[serde(rename = "decisions")] + pub decisions: Vec, + #[serde(rename = "filters")] + pub filters: Box, + #[serde(rename = "has_more")] + pub has_more: bool, + #[serde(rename = "limit")] + pub limit: i32, + #[serde(rename = "offset")] + pub offset: i32, + #[serde(rename = "project_id")] + pub project_id: uuid::Uuid, + #[serde(rename = "version")] + pub version: String, +} + +impl DecisionsListResponse { + /// One page of decisions for one project. ``has_more`` is explicit rather than inferred from ``len(decisions) == limit``: a page that happens to end exactly on the boundary is indistinguishable from a full one, and a caller that guesses wrong either stops early or pages forever. It is ADVISORY. The count and the page are two statements under read committed, so a decision captured between them can make ``has_more`` a row stale. Page until a page comes back short or empty rather than trusting it as an invariant — and expect the ordering (newest first) to shift a row onto a page you have already read if the ledger is being written while you walk it. + pub fn new(decisions: Vec, filters: models::AppliedDecisionFilters, has_more: bool, limit: i32, offset: i32, project_id: uuid::Uuid, version: String) -> DecisionsListResponse { + DecisionsListResponse { + decisions, + filters: Box::new(filters), + has_more, + limit, + offset, + project_id, + version, + } + } +} + diff --git a/wire/src/models/finding.rs b/wire/src/models/finding.rs index e75c4eb..b7cf126 100644 --- a/wire/src/models/finding.rs +++ b/wire/src/models/finding.rs @@ -11,11 +11,12 @@ use crate::models; use serde::{Deserialize, Serialize}; -/// Finding : A single coherence finding over the graph that results from a delta batch. All three v1 ``code``s are ``error``-severity; ``severity`` keeps the ``warning`` arm in the contract for future advisory codes. ``locator`` is the node / port / edge id (a string) the finding is about. +/// Finding : A single coherence finding over the graph that results from a delta batch. ``locator`` is the node / port / edge id (a string) the finding is about. **``code`` is an OPEN set and is published as a plain string on purpose.** The codes are owned by the server and new ones are added additively as new coherence rules ship. Publishing the set as a closed enum made every generated client close it too — and a generated enum with no fallback arm fails to deserialize the WHOLE response the first time the server sends a code the client has not heard of. That turns every additive rule into a coordinated release, and turns a client that is merely out of date into one that cannot read a validate response at all. So the contract is: **a consumer must tolerate codes it does not recognize** — render them, do not switch exhaustively on them. The known values are documented in the field description rather than enforced by the schema, because enforcing them here bought documentation at the cost of breaking clients. ``severity`` stays closed: it is a two-valued verdict a consumer genuinely must branch on, and adding a third value WOULD be a breaking change that deserves a coordinated release. #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct Finding { + /// Machine-readable finding code. Known values: `unsatisfied_input` (an input port with no incoming edge), `dangling_wire` (an edge whose endpoint does not exist), `type_mismatch` (a wired pair whose port types disagree). **This set is open and grows additively** — treat an unrecognized code as an opaque string and render it; do not fail on it. #[serde(rename = "code")] - pub code: Code, + pub code: String, #[serde(rename = "locator")] pub locator: String, #[serde(rename = "message")] @@ -25,8 +26,8 @@ pub struct Finding { } impl Finding { - /// A single coherence finding over the graph that results from a delta batch. All three v1 ``code``s are ``error``-severity; ``severity`` keeps the ``warning`` arm in the contract for future advisory codes. ``locator`` is the node / port / edge id (a string) the finding is about. - pub fn new(code: Code, locator: String, message: String, severity: Severity) -> Finding { + /// A single coherence finding over the graph that results from a delta batch. ``locator`` is the node / port / edge id (a string) the finding is about. **``code`` is an OPEN set and is published as a plain string on purpose.** The codes are owned by the server and new ones are added additively as new coherence rules ship. Publishing the set as a closed enum made every generated client close it too — and a generated enum with no fallback arm fails to deserialize the WHOLE response the first time the server sends a code the client has not heard of. That turns every additive rule into a coordinated release, and turns a client that is merely out of date into one that cannot read a validate response at all. So the contract is: **a consumer must tolerate codes it does not recognize** — render them, do not switch exhaustively on them. The known values are documented in the field description rather than enforced by the schema, because enforcing them here bought documentation at the cost of breaking clients. ``severity`` stays closed: it is a two-valued verdict a consumer genuinely must branch on, and adding a third value WOULD be a breaking change that deserves a coordinated release. + pub fn new(code: String, locator: String, message: String, severity: Severity) -> Finding { Finding { code, locator, @@ -37,22 +38,6 @@ impl Finding { } /// #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -pub enum Code { - #[serde(rename = "unsatisfied_input")] - UnsatisfiedInput, - #[serde(rename = "dangling_wire")] - DanglingWire, - #[serde(rename = "type_mismatch")] - TypeMismatch, -} - -impl Default for Code { - fn default() -> Code { - Self::UnsatisfiedInput - } -} -/// -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub enum Severity { #[serde(rename = "error")] Error, diff --git a/wire/src/models/inline_object_4.rs b/wire/src/models/inline_object_4.rs index 57f52de..8136803 100644 --- a/wire/src/models/inline_object_4.rs +++ b/wire/src/models/inline_object_4.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct InlineObject4 { #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] - pub detail: Option>, + pub detail: Option, } impl InlineObject4 { diff --git a/wire/src/models/inline_object_4_detail.rs b/wire/src/models/inline_object_4_detail.rs deleted file mode 100644 index 4755b98..0000000 --- a/wire/src/models/inline_object_4_detail.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * hydrate.sh v1 API - * - * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. - * - * The version of the OpenAPI document: 1.0.0-rc.1 - * - * Generated by: https://openapi-generator.tech - */ - -use crate::models; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct InlineObject4Detail { - #[serde(rename = "code")] - pub code: Code, - #[serde(rename = "message")] - pub message: String, -} - -impl InlineObject4Detail { - pub fn new(code: Code, message: String) -> InlineObject4Detail { - InlineObject4Detail { - code, - message, - } - } -} -/// -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -pub enum Code { - #[serde(rename = "name_taken")] - NameTaken, -} - -impl Default for Code { - fn default() -> Code { - Self::NameTaken - } -} - diff --git a/wire/src/models/inline_object_5.rs b/wire/src/models/inline_object_5.rs index d2db635..a6b5af4 100644 --- a/wire/src/models/inline_object_5.rs +++ b/wire/src/models/inline_object_5.rs @@ -13,14 +13,14 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct InlineObject5 { - #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] - pub detail: Option>, + #[serde(rename = "detail")] + pub detail: String, } impl InlineObject5 { - pub fn new() -> InlineObject5 { + pub fn new(detail: String) -> InlineObject5 { InlineObject5 { - detail: None, + detail, } } } diff --git a/wire/src/models/inline_object_5_detail.rs b/wire/src/models/inline_object_5_detail.rs deleted file mode 100644 index 82660ee..0000000 --- a/wire/src/models/inline_object_5_detail.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - * hydrate.sh v1 API - * - * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. - * - * The version of the OpenAPI document: 1.0.0-rc.1 - * - * Generated by: https://openapi-generator.tech - */ - -use crate::models; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct InlineObject5Detail { - #[serde(rename = "code")] - pub code: Code, - #[serde(rename = "message")] - pub message: String, -} - -impl InlineObject5Detail { - pub fn new(code: Code, message: String) -> InlineObject5Detail { - InlineObject5Detail { - code, - message, - } - } -} -/// -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -pub enum Code { - #[serde(rename = "no_fields")] - NoFields, - #[serde(rename = "invalid_name")] - InvalidName, -} - -impl Default for Code { - fn default() -> Code { - Self::NoFields - } -} - diff --git a/wire/src/models/inline_object_6.rs b/wire/src/models/inline_object_6.rs index 3c37beb..ae5db00 100644 --- a/wire/src/models/inline_object_6.rs +++ b/wire/src/models/inline_object_6.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct InlineObject6 { #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] - pub detail: Option, + pub detail: Option>, } impl InlineObject6 { diff --git a/wire/src/models/inline_object_8_detail.rs b/wire/src/models/inline_object_6_detail.rs similarity index 81% rename from wire/src/models/inline_object_8_detail.rs rename to wire/src/models/inline_object_6_detail.rs index 6b89a49..d43eca3 100644 --- a/wire/src/models/inline_object_8_detail.rs +++ b/wire/src/models/inline_object_6_detail.rs @@ -13,12 +13,12 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -pub enum InlineObject8Detail { +pub enum InlineObject6Detail { String(String), - InlineObject8DetailOneOf(Box), + InlineObject6DetailOneOf(Box), } -impl Default for InlineObject8Detail { +impl Default for InlineObject6Detail { fn default() -> Self { Self::String(Default::default()) } diff --git a/wire/src/models/inline_object_8_detail_one_of.rs b/wire/src/models/inline_object_6_detail_one_of.rs similarity index 92% rename from wire/src/models/inline_object_8_detail_one_of.rs rename to wire/src/models/inline_object_6_detail_one_of.rs index f67f6dd..73515c6 100644 --- a/wire/src/models/inline_object_8_detail_one_of.rs +++ b/wire/src/models/inline_object_6_detail_one_of.rs @@ -12,7 +12,7 @@ use crate::models; use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct InlineObject8DetailOneOf { +pub struct InlineObject6DetailOneOf { #[serde(rename = "delta_ordinal", default, with = "::serde_with::rust::double_option", skip_serializing_if = "Option::is_none")] pub delta_ordinal: Option>, #[serde(rename = "delta_type", default, with = "::serde_with::rust::double_option", skip_serializing_if = "Option::is_none")] @@ -29,9 +29,9 @@ pub struct InlineObject8DetailOneOf { pub recovery_hint: Option>, } -impl InlineObject8DetailOneOf { - pub fn new() -> InlineObject8DetailOneOf { - InlineObject8DetailOneOf { +impl InlineObject6DetailOneOf { + pub fn new() -> InlineObject6DetailOneOf { + InlineObject6DetailOneOf { delta_ordinal: None, delta_type: None, error: None, diff --git a/wire/src/models/inline_object_7.rs b/wire/src/models/inline_object_7.rs index 3f3b15e..e8256b9 100644 --- a/wire/src/models/inline_object_7.rs +++ b/wire/src/models/inline_object_7.rs @@ -13,14 +13,14 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct InlineObject7 { - #[serde(rename = "detail")] - pub detail: String, + #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] + pub detail: Option>, } impl InlineObject7 { - pub fn new(detail: String) -> InlineObject7 { + pub fn new() -> InlineObject7 { InlineObject7 { - detail, + detail: None, } } } diff --git a/wire/src/models/inline_object_9_detail.rs b/wire/src/models/inline_object_7_detail.rs similarity index 86% rename from wire/src/models/inline_object_9_detail.rs rename to wire/src/models/inline_object_7_detail.rs index 555a636..5b14bd2 100644 --- a/wire/src/models/inline_object_9_detail.rs +++ b/wire/src/models/inline_object_7_detail.rs @@ -12,16 +12,16 @@ use crate::models; use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct InlineObject9Detail { +pub struct InlineObject7Detail { #[serde(rename = "current_version", default, with = "::serde_with::rust::double_option", skip_serializing_if = "Option::is_none")] pub current_version: Option>, #[serde(rename = "error", skip_serializing_if = "Option::is_none")] pub error: Option, } -impl InlineObject9Detail { - pub fn new() -> InlineObject9Detail { - InlineObject9Detail { +impl InlineObject7Detail { + pub fn new() -> InlineObject7Detail { + InlineObject7Detail { current_version: None, error: None, } diff --git a/wire/src/models/inline_object_9.rs b/wire/src/models/inline_object_9.rs deleted file mode 100644 index 04e6784..0000000 --- a/wire/src/models/inline_object_9.rs +++ /dev/null @@ -1,27 +0,0 @@ -/* - * hydrate.sh v1 API - * - * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. - * - * The version of the OpenAPI document: 1.0.0-rc.1 - * - * Generated by: https://openapi-generator.tech - */ - -use crate::models; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct InlineObject9 { - #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] - pub detail: Option>, -} - -impl InlineObject9 { - pub fn new() -> InlineObject9 { - InlineObject9 { - detail: None, - } - } -} - diff --git a/wire/src/models/mod.rs b/wire/src/models/mod.rs index 35f4285..fff4804 100644 --- a/wire/src/models/mod.rs +++ b/wire/src/models/mod.rs @@ -2,6 +2,10 @@ pub mod add_edge_delta; pub use self::add_edge_delta::AddEdgeDelta; pub mod add_node_delta; pub use self::add_node_delta::AddNodeDelta; +pub mod anchor_ref; +pub use self::anchor_ref::AnchorRef; +pub mod applied_decision_filters; +pub use self::applied_decision_filters::AppliedDecisionFilters; pub mod apply_branch_deltas_v1_branches__branch_id__deltas_post_404_response; pub use self::apply_branch_deltas_v1_branches__branch_id__deltas_post_404_response::ApplyBranchDeltasV1BranchesBranchIdDeltasPost404Response; pub mod apply_branch_deltas_v1_branches__branch_id__deltas_post_404_response_detail; @@ -26,8 +30,16 @@ pub mod branch_meta; pub use self::branch_meta::BranchMeta; pub mod branch_not_active_body; pub use self::branch_not_active_body::BranchNotActiveBody; +pub mod branch_patch_response; +pub use self::branch_patch_response::BranchPatchResponse; pub mod branch_ref; pub use self::branch_ref::BranchRef; +pub mod decision_out; +pub use self::decision_out::DecisionOut; +pub mod decision_response; +pub use self::decision_response::DecisionResponse; +pub mod decisions_list_response; +pub use self::decisions_list_response::DecisionsListResponse; pub mod delete_edge_delta; pub use self::delete_edge_delta::DeleteEdgeDelta; pub mod delete_node_delta; @@ -64,26 +76,18 @@ pub mod inline_object_3_detail; pub use self::inline_object_3_detail::InlineObject3Detail; pub mod inline_object_4; pub use self::inline_object_4::InlineObject4; -pub mod inline_object_4_detail; -pub use self::inline_object_4_detail::InlineObject4Detail; pub mod inline_object_5; pub use self::inline_object_5::InlineObject5; -pub mod inline_object_5_detail; -pub use self::inline_object_5_detail::InlineObject5Detail; pub mod inline_object_6; pub use self::inline_object_6::InlineObject6; +pub mod inline_object_6_detail; +pub use self::inline_object_6_detail::InlineObject6Detail; +pub mod inline_object_6_detail_one_of; +pub use self::inline_object_6_detail_one_of::InlineObject6DetailOneOf; pub mod inline_object_7; pub use self::inline_object_7::InlineObject7; -pub mod inline_object_8; -pub use self::inline_object_8::InlineObject8; -pub mod inline_object_8_detail; -pub use self::inline_object_8_detail::InlineObject8Detail; -pub mod inline_object_8_detail_one_of; -pub use self::inline_object_8_detail_one_of::InlineObject8DetailOneOf; -pub mod inline_object_9; -pub use self::inline_object_9::InlineObject9; -pub mod inline_object_9_detail; -pub use self::inline_object_9_detail::InlineObject9Detail; +pub mod inline_object_7_detail; +pub use self::inline_object_7_detail::InlineObject7Detail; pub mod inline_object_detail; pub use self::inline_object_detail::InlineObjectDetail; pub mod main_branch_ref; @@ -126,6 +130,8 @@ pub mod v1_deltas_body_deltas_inner; pub use self::v1_deltas_body_deltas_inner::V1DeltasBodyDeltasInner; pub mod v1_node_position; pub use self::v1_node_position::V1NodePosition; +pub mod v1_patch_branch_body; +pub use self::v1_patch_branch_body::V1PatchBranchBody; pub mod v1_patch_project_body; pub use self::v1_patch_project_body::V1PatchProjectBody; pub mod v1_validate_body; diff --git a/wire/src/models/v1_patch_branch_body.rs b/wire/src/models/v1_patch_branch_body.rs new file mode 100644 index 0000000..655c2fe --- /dev/null +++ b/wire/src/models/v1_patch_branch_body.rs @@ -0,0 +1,30 @@ +/* + * hydrate.sh v1 API + * + * The hydrate.sh public API. All routes are scoped to the `/v1/` prefix and require a Bearer API key (see Authentication). **Stability:** `v1` is the wire-stable surface. Breaking changes will rebase to `/v2/`. + * + * The version of the OpenAPI document: 1.0.0-rc.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// V1PatchBranchBody : Body for ``PATCH /v1/branches/{branch_id}``. Rename only — a branch has no archive state the way a project does; discard (the DELETE route beside this one) is already the non-listed-but-recoverable-by-id shape a project gets from archiving. ``name`` is required (not optional-with-a- no-fields-guard, unlike the project patch body): there is only one field, so an absent body is already a 422 from Pydantic without extra code. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct V1PatchBranchBody { + /// New name for the branch. + #[serde(rename = "name")] + pub name: String, +} + +impl V1PatchBranchBody { + /// Body for ``PATCH /v1/branches/{branch_id}``. Rename only — a branch has no archive state the way a project does; discard (the DELETE route beside this one) is already the non-listed-but-recoverable-by-id shape a project gets from archiving. ``name`` is required (not optional-with-a- no-fields-guard, unlike the project patch body): there is only one field, so an absent body is already a 422 from Pydantic without extra code. + pub fn new(name: String) -> V1PatchBranchBody { + V1PatchBranchBody { + name, + } + } +} +