Skip to content

outline_node: the API accepts kinds 'arc' and 'beat' that the database forbids #263

Description

@letuhao

What

POST /v1/composition/works/{work_id}/outline/nodes advertises four node kinds:

# services/composition-service/app/db/models.py:39
NodeKind = Literal["arc", "chapter", "scene", "beat"]

The table permits two:

-- loreweave_composition, constraint outline_node_kind_check
CHECK ((kind = ANY (ARRAY['chapter'::text, 'scene'::text])))

So arc and beat pass request validation, reach the INSERT, and die on a Postgres
check constraint. The caller gets a 400 carrying the raw database error:

400 {"detail":{"code":"CONSTRAINT","detail":"new row for relation \"outline_node\"
violates check constraint \"outline_node_kind_check\"\nDETAIL: Failing row contains
(01a09ad8-2de9-..., 01a09a04-ce63-..., ..., beat, r, Beat, null, {}, , null, empty, ...)."}}

Why it matters

A documented, typed part of the API cannot be used. A client written against the schema —
or generated from it — will construct a beat node, receive a 400, and have no way to know
the kind was never storable. Nothing in the contract says two of the four are unusable.

Either the migration is missing (beat/arc were meant to be storable) or the Literal is
stale (they were removed and the type was not). The two halves disagree and only whoever
owns the outline model knows which is correct.

Secondary: the error body is a raw Postgres message

The 400 returns the constraint text and the entire failing row. It is the caller's own
row, so this is not an exposure of anyone else's data, but it does hand out column order
and internal schema, and it is not an error a client can act on. A typed
OUTLINE_NODE_KIND_UNSUPPORTED naming the accepted kinds would be both safer and usable.

How it was found

composition-telemetry.spec.ts B3.3 asserts scene_committed fires on mark-done and NOT
for a non-scene node. To test "not for a non-scene node" it creates a beat — which is why
this surfaced.

Re-measured deliberately: this spec asserts through helpers/db.ts, which until today
defaulted to the BASE stack's Postgres regardless of the browser target, so the original
observation could not be trusted. It reproduces on the isolated stack after that fix.

Not fixed here

Which side moves is a data-model decision, and guessing at it inside a test-repair change
would be the wrong place to make it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions