Skip to content

Commit c22d3da

Browse files
dmealingclaude
andcommitted
fix(docs): agent/schema.md read a DESC index as ascending
`agent/schema.md`'s stated job is "the physical shape of the `postgres` database … read it before writing a query, a migration, or anything that names a table or a column". It rendered a live `btree (ts DESC)` recency index as - `wake_events_ts_idx` · index · on `ts` The ordering IS the index for that shape: a reader deciding whether an existing index serves their ORDER BY got the opposite answer. `@where`, `@expr` and `@using` were all already rendered on the neighbouring clause — the estate report guessed `@using` was dropped too and it is not. The one member of the set missing was `orders`, and not from the renderer: the `SchemaIndexLike` input type never carried the field, so the direction never reached the page at all. migrate-ts's own descriptor has had it throughout. Rendered PER COLUMN rather than per index, because `(device_id ASC, ts DESC)` is a different index from `(device_id, ts)` and the per-column answer is the one a reader needs. An all-ascending index stays plain — `asc` on every column would be noise, and the test asserts that too. codegen-ts 1576 pass, workspace typecheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M
1 parent ca7827c commit c22d3da

3 files changed

Lines changed: 52 additions & 1 deletion

File tree

‎server/typescript/packages/codegen-ts/src/generators/agent-schema-input.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ export interface SchemaIndexLike {
3737
readonly where?: string | undefined;
3838
readonly expr?: string | undefined;
3939
readonly using?: string | undefined;
40+
/**
41+
* Per-column direction, positionally aligned with `columns`. Absent ⇒ all ascending.
42+
*
43+
* This field did not exist, so `agent/schema.md` rendered a `btree (ts DESC)` recency
44+
* index as "on `ts`" — reading as ascending on the page whose stated job is "the physical
45+
* shape of the database … read it before writing a query, a migration, or anything that
46+
* names a table or a column". The ordering IS the index for that shape. `@where`, `@expr`
47+
* and `@using` were all already rendered; this was the one member of the set the input
48+
* type never carried.
49+
*/
50+
readonly orders?: readonly ("asc" | "desc")[] | undefined;
4051
}
4152

4253
export interface SchemaFkLike {

‎server/typescript/packages/codegen-ts/src/generators/agent-schema-page.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,21 @@ function defaultCell(column: SchemaColumnLike): string {
8181
return `\`${mdCell(column.default.value)}\``;
8282
}
8383

84+
/**
85+
* The key columns, each carrying its direction when it is not the default.
86+
*
87+
* `desc` is written where it applies rather than only when the whole index is descending: a
88+
* mixed `(a ASC, b DESC)` index is a different index from `(a, b)`, and a reader deciding
89+
* whether an existing index serves their ORDER BY needs the per-column answer.
90+
*/
91+
function keyColumns(ix: SchemaIndexLike): string {
92+
return ix.columns
93+
.map((c, i) => (ix.orders?.[i] === "desc" ? `\`${c}\` desc` : `\`${c}\``))
94+
.join(", ");
95+
}
96+
8497
function indexLine(ix: SchemaIndexLike): string {
85-
const key = ix.expr !== undefined ? `\`${mdCell(ix.expr)}\`` : cols(ix.columns);
98+
const key = ix.expr !== undefined ? `\`${mdCell(ix.expr)}\`` : keyColumns(ix);
8699
const parts = [`\`${ix.name}\``, ix.unique ? "unique" : "index", `on ${key}`];
87100
if (ix.using !== undefined && ix.using !== "" && ix.using !== "btree") parts.push(`using \`${ix.using}\``);
88101
if (ix.where !== undefined && ix.where !== "") parts.push(`where \`${mdCell(ix.where)}\``);

‎server/typescript/packages/codegen-ts/test/agent-docs-surface.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,33 @@ describe("agent/schema.md", () => {
269269
...over,
270270
});
271271

272+
// F76 — a `btree (ts DESC)` recency index rendered as "on `ts`", reading as ascending on the
273+
// page whose stated job is "the physical shape of the database … read it before writing a
274+
// query, a migration, or anything that names a table or a column". The ordering IS the index
275+
// for that shape. `@where`, `@expr` and `@using` were all already rendered; `orders` was the
276+
// one member of the set the input type never carried.
277+
test("a DESC index says desc — the ordering is not decoration", async () => {
278+
const page = renderAgentSchemaPage(input({
279+
tables: [{
280+
name: "wake_events",
281+
columns: [column("id", { identity: "increment" }), column("ts"), column("device_id")],
282+
indexes: [
283+
{ name: "wake_events_ts_idx", columns: ["ts"], unique: false, orders: ["desc"] },
284+
{ name: "wake_events_device_idx", columns: ["device_id", "ts"], unique: false, orders: ["asc", "desc"] },
285+
{ name: "wake_events_plain_idx", columns: ["device_id"], unique: false },
286+
],
287+
foreignKeys: [], checks: [], primaryKey: ["id"],
288+
}],
289+
}), { declaredBy: new Map(), viewLineage: new Map(), relationships: [], enums: [] });
290+
expect(page).toContain("`ts` desc");
291+
// Mixed directions are per-COLUMN: `(device_id ASC, ts DESC)` is a different index from
292+
// `(device_id, ts)`, and a reader deciding whether it serves their ORDER BY needs that.
293+
expect(page).toContain("`device_id`, `ts` desc");
294+
// An all-ascending index stays plain — `asc` everywhere would be noise, not information.
295+
expect(page).toMatch(/wake_events_plain_idx.*`device_id`/);
296+
expect(page).not.toContain("`device_id` asc");
297+
});
298+
272299
test("a composite unique is labelled composite — a bare `unique` here would be false", async () => {
273300
const composite = input({
274301
tables: [

0 commit comments

Comments
 (0)