KUJO THE COURSE

THE KUJO COURSE · VERIFIED 1.3.1

Structured data and local persistence

Store explicit schemas and keep migrations inspectable.

STAGE 4 / Native automation · LESSON 25

Mental model

Persistence turns temporary results into future inputs. That makes a stored format an interface: tomorrow's process needs to know what the fields mean and how to reject an unsupported version.

Native contract

Kujo provides JSON and other structured-file helpers as well as database APIs. JSON serialization has deterministic ordering contracts but only accepts supported values. Convert runtime structs to selected dictionaries before serializing. Invalid JSON and non-finite numbers must not silently become valid state.

Database operations require database authority. SQLite is useful for local state; an in-memory database gives a deterministic test without leaving a file. Use parameterized query values rather than composing SQL from untrusted strings. Inspect the current db_execute and db_query signatures for your runtime.

The lesson example shows a serialization round trip for versioned file data. The native project adds an in-memory SQLite contract test. These are different storage choices around the same domain model, not reasons to leak database row encodings into every function.

Professional pattern

Validate before saving and again when loading. Include a schema version. For a format change, write a migration with fixtures covering old and new state. Prefer atomic publication when the native write contract supports it, and retain failure evidence instead of leaving a partially written file that looks complete.

A local database is not automatically private. File permissions, backups, and the process's authority remain relevant. Do not store raw credentials or entire model prompts when an identifier or redacted receipt with explicit size limits would suffice.

Common mistakes

Do not use a timestamp as the only identity for reproducible evidence. Do not equate deterministic serialization with semantically correct content. A perfectly stable JSON object can still contain the wrong count. Test the calculation independently of the round trip.

Working example

let state := {"schema": "course.state/v1", "completed": ["report"]}
let encoded := to_json(state)
let restored := parse_json(encoded)
assert_equal(restored, state)
print(encoded)

Run it

From the course repository root, use the pinned Kujo 1.3.1 runtime.

kujo check examples/25.kujo
kujo run --untrusted  examples/25.kujo

Captured output

{"completed":["report"],"schema":"course.state/v1"}

Break it and diagnose it

The persisted representation is malformed. Loading must reject it rather than return an apparently empty state.

print(parse_json("{broken"))

kujo run --untrusted  examples/25-break.kujo

Exit status: 4. Captured diagnostic:

[KUJOVM001] [vm] Runtime Error: JSON parse error: key must be a string at line 1 column 2
  --> 0:0


Exercise

Create a versioned local state file and a migration fixture. Validate both before and after serialization. Run the supplied SQLite test and explain which capability it needs independently of file flags.

Checkpoint

  • I treat stored data as a versioned interface.
  • I test values as well as serialization.
  • I keep database and filesystem authority distinct.

Verified 2026-09-06 · Official Kujo 1.3.1 release · Source contract · Download example