Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
75ea90b
docs(backlog): file D70, the DuckDB multi-statement claim the tail re…
cevheri Sep 13, 2026
3e78cbf
docs(backlog): file five defects measured through the product, and ex…
cevheri Sep 13, 2026
9270b9f
docs(backlog): file D74 and D75, the two boundaries the transaction f…
cevheri Sep 13, 2026
da2eaaa
feat(objects): the object edit contract, its derivations and the cens…
cevheri Sep 13, 2026
6868a3d
fix(objects): refuse a half declaration and drive the edit walk's gre…
cevheri Sep 14, 2026
123acfe
feat(ui): the object source pane's editable predicate (#789)
cevheri Sep 14, 2026
ee3d999
feat(objects): core's edit helpers, the coordinate map and the object…
cevheri Sep 14, 2026
4ce058e
fix(ui): pin the editable predicate's reason guard and correct two te…
cevheri Sep 14, 2026
e32f4e1
test(objects): bind each consequence class to its own sentence and pi…
cevheri Sep 14, 2026
86c8c55
feat(ui): a bounded draft store for unsaved object edits (#789)
cevheri Sep 14, 2026
d3b450d
feat(api): runtime shapes for the object edit wire (#789)
cevheri Sep 14, 2026
004e59e
test(ui): build the population the array guard in the draft store is …
cevheri Sep 14, 2026
51c7d88
test(api): pin the conflict arm's second discriminant by value (#789)
cevheri Sep 14, 2026
2624225
fix(ui): give every draft-store guard a population, and stop trusting…
cevheri Sep 14, 2026
ee59263
fix(api): bind the segment map to its bytes and see a hidden own prop…
cevheri Sep 14, 2026
db6ab70
fix(ui): build the array guard's real population and name the drop's …
cevheri Sep 14, 2026
10a3617
feat(api): seal an object edit plan with a per-leaf digest and a deri…
cevheri Sep 14, 2026
7ee2efe
feat(ui): the object apply preview dialog (#789)
cevheri Sep 14, 2026
a46e1da
fix(api): give the plan seal's framing and discriminants a real popul…
cevheri Sep 14, 2026
351d007
fix(ui): make the apply dialog refuse the outcomes it cannot honestly…
cevheri Sep 14, 2026
d7cc9ce
feat(api): add the EDIT_PLAN_INVALID error code (#789)
cevheri Sep 14, 2026
0f57948
feat(api): bound an object edit body and enforce the edit affordance …
cevheri Sep 14, 2026
2d038f8
docs(api): separate the two evidence classes behind the body bound (#…
cevheri Sep 14, 2026
8879d11
docs(api): name the line the body clone is installed on, and that it …
cevheri Sep 14, 2026
b27bbc7
feat(objects): apply an edited definition on PostgreSQL functions and…
cevheri Sep 14, 2026
625749c
test(objects): name the SQLSTATE arm the classifier table test goes r…
cevheri Sep 14, 2026
18f5150
fix(api): correct the body-clone claim and pin the two body reads (#789)
cevheri Sep 14, 2026
f6c01e7
fix(api): point the divergence claim at the test file that measures i…
cevheri Sep 14, 2026
6fdcc8b
fix(objects): ask whether the addressed row was rewritten, not whethe…
cevheri Sep 14, 2026
f53eb83
docs(ui): the route's edit stripping is written, so stop saying it is…
cevheri Sep 14, 2026
779a2a7
feat(ui): the object apply seam, build and apply against the two edit…
cevheri Sep 14, 2026
52ed628
feat(ui): the host-trust arm for a part's edit affordance (#789)
cevheri Sep 14, 2026
f8ff9db
feat(ui): re-export the apply seam for both shells (#789)
cevheri Sep 14, 2026
7579d08
feat(objects): apply an edited definition on Trino functions (#789)
cevheri Sep 14, 2026
618297b
test(objects): pin the one coordinate an unanchored Trino splice move…
cevheri Sep 14, 2026
15efe60
docs(trino): the object edit section, and correct one docblock claim …
cevheri Sep 14, 2026
f51f657
fix(ui): bound the edit reason on the arm that renders it (#789)
cevheri Sep 14, 2026
4edda93
fix(objects): ask which object Trino wrote, not whether its rendering…
cevheri Sep 14, 2026
dd4cbdc
fix(objects): name the overload Trino wrote in a shape a reader can l…
cevheri Sep 14, 2026
110aba8
test(objects): state the fork rule the Trino guard actually implement…
cevheri Sep 14, 2026
07a67f1
feat(api): the object edit plan and apply routes (#789)
cevheri Sep 14, 2026
4884755
docs(api): the two object edit routes, field by field (#789)
cevheri Sep 14, 2026
1d6ac88
feat(objects): apply an edited function library on Redis (#789)
cevheri Sep 14, 2026
4a7dda4
test(objects): kill the two Redis mutations the brief's set left aliv…
cevheri Sep 14, 2026
5041310
test(objects): pin the audit fields nothing was killing, and unplant …
cevheri Sep 14, 2026
26a8d2b
fix(objects): bind the Redis reply, affordance and revision checks to…
cevheri Sep 14, 2026
0c96b49
docs(api): re-count stripEdit's producers, and say how rule 2 changed…
cevheri Sep 14, 2026
b2f2201
fix(ui): a truncated part never claims completeness in the caption (#…
cevheri Sep 14, 2026
2d207a0
feat(security): audit and document the object edit control (#789)
cevheri Sep 14, 2026
7d595bc
test(security): bind control 3.6 to the plan id and the plan token (#…
cevheri Sep 14, 2026
0fda277
feat(ui): edit an object definition in the source pane, behind a mand…
cevheri Sep 14, 2026
c686127
test(objects): a foreign draft write is not an eviction, which nothin…
cevheri Sep 14, 2026
29babff
fix(ui): key a pending draft write and an eviction watch on the part …
cevheri Sep 14, 2026
68753d6
test(objects): a failed draft write draws no banner over the part the…
cevheri Sep 14, 2026
9230cad
test(monaco): give both doubles the DiffEditor export the real module…
cevheri Sep 14, 2026
960f67b
test(objects): register the edit census group and export the census c…
cevheri Sep 14, 2026
807bf9b
test(objects): census the fleet's edit declarations against the commi…
cevheri Sep 14, 2026
b423bea
feat(ui): mark an unsaved object edit in the tab strip (#789)
cevheri Sep 14, 2026
a68b647
docs(providers): name which absence each of the fourteen abstainers i…
cevheri Sep 14, 2026
5a5538c
feat(ui): wire the object apply into the standalone shell (#789)
cevheri Sep 14, 2026
4fac09b
test(objects): guard that every abstainer's doc carries its edit sect…
cevheri Sep 14, 2026
a3f7e54
fix(ui): announce the re-read, and say why the clear reaches the righ…
cevheri Sep 14, 2026
77015fc
feat(workspace): an optional host object editor for the embedded shel…
cevheri Sep 14, 2026
615a483
test(workspace): put the refused arm in the failed-apply test's popul…
cevheri Sep 14, 2026
7784264
docs(providers): the measured object edit acceptance run on PostgreSQ…
cevheri Sep 14, 2026
b23561c
fix(workspace): copy the host's answer while measuring it (#789)
cevheri Sep 14, 2026
1ae18c1
docs(providers): correct the PostgreSQL acceptance-run headline and r…
cevheri Sep 14, 2026
7957817
docs(backlog): attribute D76's Trino and Redis refusals to the review…
cevheri Sep 14, 2026
eec65d0
fix(ui): use output for the re-read announcement, which jsx-a11y requ…
cevheri Sep 14, 2026
38d9054
test(e2e): drive an object edit end to end under the production CSP (…
cevheri Sep 14, 2026
5fb78b6
test(e2e): put the object-edit spec in the only CI job with a Docker …
cevheri Sep 14, 2026
b3aa682
fix(postgres): terminate the reader's line before the statement on an…
cevheri Sep 14, 2026
3a2b4c9
test(postgres): cover both routine kinds and pin the four-endings pop…
cevheri Sep 14, 2026
ca67a78
Merge origin/main into feat/object-ddl-apply-phase-3 (#789)
cevheri Sep 14, 2026
c65ed74
docs(backlog): close D71, D72 and X17, which are fixed on main and he…
cevheri Sep 14, 2026
f0c2940
docs(backlog): file D78, an object edit on a poisoned pooled client (…
cevheri Sep 14, 2026
3e98f16
fix(objects): refuse an object edit on a pooled client somebody else …
cevheri Sep 14, 2026
d84d609
docs: keep the facts X17 stood for, now that the id no longer resolve…
cevheri Sep 14, 2026
653060e
Merge origin/main into feat/object-ddl-apply-phase-3 (#789)
cevheri Sep 14, 2026
bfad735
docs(backlog): file the seven #789 Phase 3 residuals and correct two …
cevheri Sep 14, 2026
0c5e3a2
docs(api): both route censuses said seven of nine directories exist, …
cevheri Sep 14, 2026
f190325
test(studio): defend the standalone Source pane's dirty prop, which n…
cevheri Sep 14, 2026
9c42d55
fix(objects): name the audit reading's shape, which was two uncoverab…
cevheri Sep 14, 2026
1b643dc
docs(backlog): file X22, the object-edit E2E's restored-tab test is f…
cevheri Sep 14, 2026
ca378fc
build(sonar): suppress S2871 on the plan seal, where localeCompare wo…
cevheri Sep 14, 2026
7f2e713
docs(api): drop the duplicate S2871 note the seal's sort picked up (#…
cevheri Sep 14, 2026
42de300
docs(backlog): X22 fails its first attempt on every CI run, not inter…
cevheri Sep 14, 2026
6c31c78
fix(security): seal an object edit plan against the connection URI as…
cevheri Sep 14, 2026
2acd31a
fix(ui): bind the object source pane's edit state to the object it is…
cevheri Sep 14, 2026
c5cc090
fix(objects): refuse a build answer addressed to another object (#789)
cevheri Sep 14, 2026
3b7d693
fix(security): seal an object edit plan against its SSH tunnel too (#…
cevheri Sep 14, 2026
452d0c2
fix(objects): pin the object name onto the sealed plan, and drop a gu…
cevheri Sep 14, 2026
9864846
test(trino): drive the build's overload resolver against the apply's …
cevheri Sep 14, 2026
9c2adb4
test(objects): assert the apply contract by value, and the tick nothi…
cevheri Sep 14, 2026
d858146
docs(backlog): file the four minor items of the #831 review that repr…
cevheri Sep 14, 2026
f1cea02
test(objects): land a plan the tab under test can accept, so the guar…
cevheri Sep 14, 2026
c9ac416
test(objects): correct two docblock measurements and assert the plan …
cevheri Sep 14, 2026
cdf85b1
docs(backlog): narrow X24's safety claim and file the case it was wro…
cevheri Sep 14, 2026
e3bc168
docs(api): move the API doc's version heading to the one it documents…
cevheri Sep 14, 2026
2523f2d
fix(workspace): re-read the definition the embedded shell just applie…
cevheri Sep 14, 2026
3a4511a
fix(redis): warn about a one-function library's function, which an ap…
cevheri Sep 14, 2026
421040e
test(e2e): name X22's cause and make the restored-tab test pass its f…
cevheri Sep 14, 2026
18a99ce
fix(object-source): report what an apply destroyed, on a screen a rea…
cevheri Sep 14, 2026
ab198aa
docs(object-source): correct an ordering claim the mutation refuted (…
cevheri Sep 14, 2026
c2437ec
fix(postgres): end the identity header at the parameter list, not at …
cevheri Sep 14, 2026
4b173a7
test(postgres): make the header scanner's two cross-quote guards non-…
cevheri Sep 14, 2026
a8bba7a
docs(postgres): name the two mixed-quote renderings the header scan's…
cevheri Sep 14, 2026
5db8e8f
fix(trino): compare an edited object's IDENTITY and not its first lin…
cevheri Sep 14, 2026
130c82e
fix(trino): make the post-apply control ask the IDENTITY, not the sig…
cevheri Sep 14, 2026
afa7004
fix(object-source): keep the collateral report on screen when the she…
cevheri Sep 14, 2026
3dcba95
fix(object-edit): frame the apply dialog by what happened, not by wha…
cevheri Sep 14, 2026
5c145d8
refactor(object-edit): name the apply report an OUTCOME region, and p…
cevheri Sep 14, 2026
7882a80
fix(trino): compare an identity by Trino's own identifier rules, not …
cevheri Sep 14, 2026
771e650
fix(object-edit): put the three lines under the outcome into the past…
cevheri Sep 14, 2026
c89a312
fix(trino): give the post-apply control a question the identity reade…
cevheri Sep 14, 2026
f7427dd
test(trino): pin the two steps the identity reading still drops, and …
cevheri Sep 14, 2026
aa3815f
fix(object-edit): stop the apply screen predicting an apply that alre…
cevheri Sep 14, 2026
54febf8
test(object-edit): read the completed apply on the medium that actual…
cevheri Sep 14, 2026
76a083e
test(postgres): record the header population as the seven it is, and …
cevheri Sep 14, 2026
02b1bb5
docs(object-edit): correct the testid handover's count, which its own…
cevheri Sep 14, 2026
90daeee
docs(postgres): correct three identity-check claims the header fix fa…
cevheri Sep 14, 2026
c3761ab
docs(object-edit): name the two things a mechanical testid rename get…
cevheri Sep 14, 2026
2f9c1bc
fix(postgres): withdraw an unmeasured rename claim and bind the doc's…
cevheri Sep 14, 2026
aa9170e
refactor(object-edit): rename the apply outcome testid, across all si…
cevheri Sep 14, 2026
d429a65
test(postgres): remove the two prose counts fix round 1 left unbound …
cevheri Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docker/postgres-init/03-object-fixture.sql
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,45 @@ CREATE TABLE IF NOT EXISTS public.orders (
-- family applies no privilege check at all.
DROP ROLE IF EXISTS src_probe;
CREATE ROLE src_probe LOGIN PASSWORD 'src_probe';

-- Phase 3 (#789). The object the READ BOUND actually bites, and its control.
--
-- Until this existed, the rule "a part carrying `truncated` is never editable" was a guard over an
-- EMPTY POPULATION on every engine in the fleet: the Phase 1 and Phase 2 conformance fixtures were
-- built short, docs/providers/postgres.md records definitions of 119 to 606 characters, and
-- SOURCE_CHARACTER_LIMIT is 1,000,000. The browser probe built one of 1,275,140 characters by hand
-- and it is the only truncation population that has ever existed for this engine.
--
-- A `DO` block that BUILDS the body rather than a 1.2 MB literal committed into an init script: the
-- file stays small and the object is the same size every time.
DO $fixture$
DECLARE
filler text := repeat('-- padding for the object source bound, #789' || chr(10), 27000);
BEGIN
EXECUTE 'CREATE OR REPLACE FUNCTION app.over_limit_fn(n integer) RETURNS integer LANGUAGE sql AS $body$' ||
chr(10) || filler || 'SELECT n;' || chr(10) || '$body$';
END
$fixture$;

-- The CONTROL, just UNDER the bound. Without it the truncation assertion cannot distinguish "the
-- bound bit" from "this object is large", which is the difference between a measurement and a
-- coincidence. The browser probe measured the same shape at 975,134 characters.
DO $fixture$
DECLARE
filler text := repeat('-- padding for the object source bound, #789' || chr(10), 21000);
BEGIN
EXECUTE 'CREATE OR REPLACE FUNCTION app.huge_fn(n integer) RETURNS integer LANGUAGE sql AS $body$' ||
chr(10) || filler || 'SELECT n;' || chr(10) || '$body$';
END
$fixture$;

-- The ownership refusal's population, and the control that makes it non-vacuous (#789 Phase 3).
--
-- MEASURED on 18.4: these grants do NOT make the apply work, because `CREATE OR REPLACE` on
-- somebody else's object is an OWNERSHIP check and not a privilege check. The refusal is
-- `must be owner of function order_total`, SQLSTATE 42501, and the shipped error mapper turns it
-- into an HTTP 500 because the message matches none of its substrings. The CONTROL is in the same
-- session: this same role creating an object OF ITS OWN in this same schema succeeds, so the
-- refusal is about ownership rather than about the schema or the role.
GRANT USAGE ON SCHEMA app TO src_probe;
GRANT CREATE ON SCHEMA app TO src_probe;
1 change: 1 addition & 0 deletions docker/redis-init/01-object-fixture.redis

Large diffs are not rendered by default.

807 changes: 807 additions & 0 deletions docker/trino-init/01-object-fixture.sql

Large diffs are not rendered by default.

132 changes: 131 additions & 1 deletion docs/API_DOCS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LibreDB Studio API Documentation

> **Version:** 0.14.1
> **Version:** 0.16.0
> **Base URL:** `https://your-domain.com` or `http://localhost:3000`
> **Content-Type:** `application/json`

Expand Down Expand Up @@ -789,6 +789,134 @@ A `druid` connection fails the second check whatever the `type` is, with `{ "err

A `trino` connection passes it for `kill` and fails it for everything else, which is the difference between an empty supported set and a set of one: `CALL system.runtime.kill_query` really terminates a statement (verified end to end - the target then fails `ADMINISTRATIVELY_KILLED`), while vacuum, reindex, optimize, check and analyze all describe work that belongs to the connector behind a catalog rather than to the engine.

#### POST /api/db/objects/edit-plan

Build a plan for an edited object definition, and answer what an apply would send.
It executes nothing and writes nothing.

These two routes are the first object routes documented in this file at all.
Their seven Phase 2 siblings under `/api/db/objects/` are not documented here yet.

**Authentication:** Required.
There is NO admin gate on either route, and the reason is measured rather than preferred: a
`user`-role session already creates and drops routines through `POST /api/db/query`, so the role
decides which connection may be OPENED and nothing about what may be done with it.
A seed connection that the caller's role is not admitted to still answers `403`, unchanged, because
both routes resolve the connection through the same seed filter every other database route uses.

**Request:**
```json
{
"connection": { "id": "conn-123", "type": "postgres", "host": "localhost", "port": 5432, "database": "mydb", "user": "app" },
"path": ["app", "order_total(integer)"],
"kind": "function",
"partId": "definition",
"text": "FUNCTION app.order_total(integer) RETURNS integer LANGUAGE sql AS $$ SELECT 1 $$"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `connection` or `connectionId` | object or string | Yes | The same connection selector every database route takes |
| `path` | string[] | Yes | The object's path, container segments first. An empty array is refused |
| `kind` | string | Yes | The object kind id, as the CONNECTED provider declares it |
| `partId` | string | Yes | Which part of the definition this text is. A package has more than one |
| `text` | string | Yes | The reader's edited text for THAT part, at most 1,000,000 characters |

**Response (200 OK), a build that planned:**
```json
{
"built": true,
"plan": { "planVersion": 1, "planId": "...", "issuedAt": "...", "connectionFingerprint": "...", "type": "postgres", "path": ["app", "order_total(integer)"], "kind": "function", "partId": "definition", "strategy": "guarded-atomic-batch", "unit": { "medium": "statement", "steps": [{ "text": "...", "language": "pgsql", "segments": [] }] }, "session": [], "revision": { "check": "guarded", "token": "...", "basis": "pg_proc.xmin", "scope": "server" }, "consequences": [] },
"preimage": { "text": "...", "language": "pgsql" },
"planToken": "<JWS>"
}
```

`plan.unit` is the exact artifact the apply will send, byte for byte.
`preimage` is the definition the build READ, for the preview's left side, and it rides in the
response rather than inside the plan: a plan carrying both would be about 12 MB inbound on the
apply and would be silently truncated by the framework at 10,485,760 bytes.
`planToken` seals the plan; the apply refuses a plan whose bytes do not match the token it arrives
with.
The plan is readable and unforgeable, which are different properties: a client may read every field
and may not change one.

**Response (200 OK), a build that REFUSED:**
```json
{
"built": false,
"refusal": { "refusal": "privilege", "sentence": "must be owner of function order_total", "code": "42501", "at": { "within": "none" } }
}
```

A deliberate engine refusal is a `200` carrying a typed verdict and never a `4xx` or a `5xx`.
The request was well formed and was carried out; what the engine said is the payload.

#### POST /api/db/objects/edit-apply

Send a plan issued by `edit-plan`, and answer what the engine did.
This is the only route in this product that writes a user's database object, and it is the first
one whose action is recorded in the audit log.

**Authentication:** Required. No admin gate, for the reason stated above.

**Request:**
```json
{
"connection": { "id": "conn-123", "type": "postgres" },
"plan": { "planVersion": 1, "planId": "..." },
"planToken": "<JWS>",
"acknowledged": ["replaces-whole-container"]
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `connection` or `connectionId` | object or string | Yes | Must resolve to the SAME server the plan was built against |
| `plan` | object | Yes | The plan `edit-plan` answered, unchanged |
| `planToken` | string | Yes | The token `edit-plan` answered beside it |
| `acknowledged` | string[] | Only when the plan names consequences | One entry per `plan.consequences[].loses`. The check is enforced by the SERVER |

**Response (200 OK):** one arm of the outcome union, always with a `duration` in milliseconds.

| `outcome` | Meaning | Also carries |
|-----------|---------|--------------|
| `applied` | The addressed object was replaced and nothing else was destroyed | `revision` |
| `applied-with-collateral` | Replaced, AND something the plan warned about was destroyed | `lost`, `revision` |
| `applied-elsewhere` | The engine accepted the text and the addressed object is not what changed | `undone`, optional `wrote` |
| `conflict` with `conflict: "object-changed"` | The object moved between the read and the write. Nothing was executed | `current`, for the diff |
| `conflict` with `conflict: "engine-refused-concurrent"` | Refused on concurrency, not content. Nothing changed and the same plan may be sent again | `sentence`, optional `code` |
| `refused` | The engine refused the write. Nothing changed | `refusal` |
| `interrupted` | The statement was sent and no readable answer came back | `committed`, `sentence` |

There is NO `retryable` field on this route at any status.
A client that retries an apply whose disposition is unknown applies twice.
Once the provider has been called, no error escapes as an HTTP error: a throw and an unreadable
answer both become `interrupted` with `committed: "unknown"` at `200`.

**Statuses, both routes:**

| Condition | Status | Body |
|-----------|--------|------|
| Any DECIDED provider answer: a build that planned, a build that refused, and all seven apply outcomes | `200` | the typed union above |
| Caller mistakes decided from the DECLARATION: undeclared kind, kind not editable, `path` not a path, missing `partId`, malformed plan, a build answering both a plan and a refusal, an unacknowledged required consequence | `400` | `{ "error": "..." }` |
| Plan token invalid, expired, digest mismatch, wrong connection fingerprint, unknown `planVersion` | `400` | `{ "error": "...", "code": "EDIT_PLAN_INVALID" }` |
| Body above 8,388,608 bytes, or `text` above 1,000,000 characters | `413` | `{ "error": "..." }` |
| No session | `401` | `{ "error": "Authentication required" }` |
| Seed connection not available for the caller's role | `403` | the existing `SeedConnectionError` body |
| Rate limited | `429` | `{ "error": "...", "code": "RATE_LIMITED" }` |
| A throw BEFORE the provider call | as `createErrorResponse` maps it | inherited |
| Anything undeclared | `500` | `createErrorResponse`'s body |

**Audit.**
One apply emits exactly two `object_edit` events under one `correlationId`, which is `plan.planId`:
a DECISION event with `action: "PLAN"` before the provider is called, and an OUTCOME event with
`action` set to the plan's strategy after it.
Neither event ever carries the statement, the command payload, the reader's text, the pre-image, the
engine's message, the engine's code, the revision token or the plan token.
A plan the seal refuses emits ONE event, with `reason: "object_edit_plan_invalid"`.

---

### AI API
Expand Down Expand Up @@ -1345,6 +1473,7 @@ interface ActiveSession {
| `401` | Unauthorized - Missing or invalid authentication |
| `403` | Forbidden - Insufficient permissions, or the request's Origin does not match this deployment (`ORIGIN_MISMATCH`) |
| `408` | Request Timeout - Query exceeded time limit |
| `413` | Payload Too Large - the request body, or one part's text, is above the object edit routes' own bound |
| `429` | Too Many Requests - Rate limit exceeded. Applies to `POST /api/auth/login` and every session-guarded route (see "Rate Limiting" below), not only the AI endpoints |
| `499` | Client Closed Request - Query cancelled by the client |
| `500` | Internal Server Error |
Expand Down Expand Up @@ -1384,6 +1513,7 @@ These are the values of the `code` field emitted by `createErrorResponse` (`src/
| `INTERNAL_ERROR` | Unhandled server error (500) |
| `NETWORK_ERROR` | Network failure |
| `RATE_LIMITED` | Application-level rate limit exceeded (429) - see "Rate Limiting" below |
| `EDIT_PLAN_INVALID` | An object edit plan did not verify: forged, expired, digest mismatch, wrong connection fingerprint, or an unknown `planVersion` (400). Returned by the two `/api/db/objects/edit-*` routes directly rather than through `createErrorResponse`. It is a machine-readable code and not a sentence, so a client can tell a plan that no longer verifies apart from an apply that failed and offer to rebuild the preview; no shipped UI branches on it yet |

The Origin-mismatch 403 (see "CSRF: Origin Check" below) is not in this table: it is returned
directly by the request middleware (`src/proxy.ts`), before a request ever reaches
Expand Down
Loading
Loading