v0.8.3: db generate Actually Writes Files, and Schemas Stop Truncating Your Strings
The v0.8 release in March closed the gap between "compiles" and "works" for scaffolded code — status transitions, associations, pagination, actor extraction, real error codes. Once that landed, AI agents started using SysMARA on bigger, more realistic projects. And they found two more papercuts.
Both are fixed in v0.8.3. Both are the kind of bug that makes you doubt the whole stack until you notice them, and then makes you laugh.
1. sysmara db generate didn't write anything
Run npx sysmara db generate. The CLI prints:
$ npx sysmara db generate
Using adapter: sysmara-orm (postgresql)
Generated schema for 4 entities
No errors. Sounds like it worked. So you go look for the file. There is no file. There is no
app/database/ directory. There is no SQL anywhere.
The command had been generating the schema content correctly the whole time — but only
returning it to the caller via the --json mode payload. The default execution path
happily logged "Generated N file(s)" and then never wrote them. Every consumer hit this and was
surprised. The command name said one thing; the behaviour did the opposite.
The fix in v0.8.3 is the obvious one: db generate and db migrate now
persist their output. The output directory is resolved in this order:
database.outputDirfromsysmara.config.yaml— the valuesysmara initalready writes (default./app/database).generatedDiras a fallback for projects that omitoutputDir.
The --json payload also got more useful: it now includes outputDir and
the absolute paths of every written file, so an AI agent can chain a follow-up
step (run psql against the generated SQL, hand the path to a code review tool,
etc.) without re-deriving paths from the config.
$ npx sysmara db generate
Using adapter: sysmara-orm (postgresql)
Generated schema for 4 entities
Wrote 1 file(s) to /abs/path/app/database
Four integration tests pin the new behaviour: the happy path, the generatedDir
fallback, and the --json payload shape.
2. Every type: string field became VARCHAR(255)
The other papercut showed up the moment anyone tried to store something larger than a tweet.
The sysmara-orm schema generator collapsed every type: string field to
VARCHAR(255) — regardless of intent.
In real-world specs that meant:
- JSON-shaped payloads stored as
VARCHAR(255), silently truncated. - Long-form prose (
description,recommendation,body) capped at 255 bytes, with no warning. created_at/updated_atdeclared astype: date— the init template's own convention — emitted as calendarDATEcolumns with no time component.
This is exactly the kind of bug AI agents struggle to reason about. The spec is valid. The schema generator runs without errors. The first row inserts fine. The second row triggers a truncation a week later when a user finally hits the limit, in production. There is nothing in the trace that points back to the schema generator's default rule.
The fix: four heuristics, all gated
The v0.8.3 schema generator looks at signals already present in the spec and promotes the
column type only when those signals are clear. Existing specs without any of these signals
keep their existing VARCHAR(255) — nothing breaks.
1. Description-flagged JSON
If a type: string field's description mentions JSON, JSONB,
Record<…>, or T[] arrays, it now emits JSONB
(PostgreSQL) / JSON (MySQL).
# entities.yaml
- name: webhook_event
fields:
- name: payload
type: string
description: "Raw webhook body, stored as JSON" -- v0.8.2 (postgres)
"payload" VARCHAR(255) NOT NULL -- silently truncated
-- v0.8.3 (postgres)
"payload" JSONB NOT NULL
This rewards the existing pattern of "use the safe lowest-common type and document the shape"
without forcing a separate type: json that AI agents would have to learn about.
2. Long-text by name
Common names that mean prose — description, content,
body, notes, comment, recommendation,
summary, excerpt, bio, about,
markdown, html, text, message
— emit TEXT instead of VARCHAR(255).
Same for suffixes: *_html, *_md, *_content,
*_body, *_description, *_text, *_notes.
3. maxLength constraint sizing
If you attach { type: maxLength, value: 320 } to a string field, the column
becomes VARCHAR(320) instead of always being capped at 255. RFC-compliant emails
finally fit.
4. *_at date promotion
Fields declared as type: date whose name ends in _at — or
matches created_at, updated_at, deleted_at —
become TIMESTAMPTZ on PostgreSQL and DATETIME(3) on MySQL. SQLite
still uses TEXT, which already stores both.
# entities.yaml
- name: post
fields:
- name: created_at
type: date
required: true
- name: published_at
type: date -- v0.8.3 (postgres)
"created_at" TIMESTAMPTZ NOT NULL,
"published_at" TIMESTAMPTZ
SQLite output is unchanged for these heuristics — TEXT covers both strings
and JSON natively, and the SQLite date format already encodes the time component.
Back-compat is the whole point
Every heuristic above only fires when the spec gives a clear signal. A field named
email with no maxLength, no description hint, and no suffix still
emits VARCHAR(255). The 9 new generator tests pin this back-compat
explicitly — if you have an existing project on v0.8.2, upgrading to v0.8.3 will not
rewrite any column types you didn't ask to be rewritten.
The total test count went from 390 to 399. The new tests cover both directions: the new behaviour where the heuristic should promote, and the back-compat case where it should not.
Why both fixes belong in the same release
They look unrelated at first — one is a CLI persistence bug, the other is a column-type inference improvement — but they share an origin. Both came from AI agents running SysMARA on real projects, hitting "this looks like it worked" behaviour, and only later discovering that the result didn't match what the docs (and the command name) implied.
SysMARA's whole bet is that AI agents can read the system graph and reason about behaviour
without humans in the loop. That bet only pays off if every layer behaves the way it
advertises. db generate has to actually generate. VARCHAR(255) has
to be the default, not the universal answer. v0.8.3 is two more places where the layer now
matches the contract.
Upgrade
npm install @sysmara/core@0.8.3
No spec changes required. Run sysmara db generate after upgrading and inspect
the output — that's the fastest way to see both fixes in action.