STAGE 3 / Projects and tooling · LESSON 17
Why this exists
Kujo has a broad native library. Memorizing names is less useful than knowing how to discover a function's actual contract. A plausible name or familiar equivalent from another language is not sufficient evidence.
Read the inventory
The standard-library table records signatures, arity, return types, error behavior, capability gates, and examples. Some rows say handler-defined; inspect the handler and its tests when the table does not specify enough detail. This is especially important for network, database, and process APIs.
Major families include strings and collections; files and paths; JSON, TOML, YAML, and CSV; hashing and crypto; HTTP and sockets; processes and environment; databases; asynchronous helpers; formatting and inspection; and the AI mechanisms covered later.
Four questions
Before using a native operation, answer: What arguments are accepted? What exact shape comes back? How is failure represented? What authority does it require? Then run a minimal example and an invalid-input drill.
The example parses a small JSON document, selects a field, and serializes a deliberate result. parse_json accepts JSON root values, not just dictionaries, so a successful parse alone does not establish that the result has the expected structure. Stage 5 introduces schema validation as a general boundary tool.
Professional pattern
Wrap a native operation only when the wrapper adds a real application policy: input limits, a stable domain result, or an explicit retry decision. Avoid layers that merely rename every builtin; they obscure the canonical contract and multiply documentation work.
Common mistakes
Display output and JSON serialization are not interchangeable. A runtime struct may print but fail to_json. Invalid JSON is an error, not an empty dictionary. Capability-free parsing does not make arbitrary parsed data trustworthy. It only tells you that parsing itself performs no host effect.
Working example
let data := parse_json(to_json({"items": [2, 3]}))
assert_equal(len(data["items"]), 2)
print(to_json({"count": len(data["items"])}))
Run it
From the course repository root, use the pinned Kujo 1.9.0 runtime.
kujo check examples/17.kujo
kujo run --untrusted examples/17.kujo
Captured output
{"count":2}
Break it and diagnose it
The text is malformed JSON. The native parser returns a real parse-location failure instead of silently producing empty data.
print(parse_json("{broken"))
kujo run --untrusted examples/17-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
Choose one string helper and one filesystem helper from the inventory. Record their arity, result, failure, and capability. Write a valid and invalid call for each, using a fixture for the filesystem case.
Checkpoint
- I consult the canonical builtin inventory.
- I distinguish parsing from validation.
- I know how to inspect handler-defined contracts.
Bounded data APIs added in 1.4.0
The native inventory now includes HTML tokenization, URL normalization/components, bounded text decoding, streaming XML selection, JSON/JSONL file operations, external sorting, regular-file digests, and no-replace publication. These mechanisms do not implement crawling rules, retries, report schemas, or retrieval policy.
Use each API's explicit byte, event, depth, or row limits; their larger explicit bounds do not raise the existing buffered file/network defaults. XML projection rejects DTD/entity declarations and bounds gzip expansion. JSONL append may leave partial staged output on failure and is not transactional. Keep staging cleanup in the application.
The pure examples/supplemental/v1-4-values.kujo checks HTML tokenization and URL normalization without network access. Consult the tagged native inventory for each operation's arity, bounds, failure shape, and primary/secondary capabilities.
Documents and dates in 1.5.0
pdf_render_html accepts a strict HTML/CSS business-document profile, explicit byte assets, and bounded options. It renders in-process without filesystem or network access. It is not a browser: JavaScript, external stylesheets, SVG, remote asset URLs, and arbitrary markup are outside the profile. The file variant, pdf_render_html_to_file, requires filesystem-write, an absolute destination with an existing parent, and atomic no-replace publication.
The supplemental v1-5-pdf.kujo example verifies a repeatable output digest for identical input under the installed renderer. Do not compare render_duration_ms as deterministic evidence. The release verifier also checks rejection of a remote image. Consult the PDF profile for bounds and fonts. Tagged-PDF accessibility, PDF/A, signatures, and general complex-script shaping are not claimed. A render deadline stops waiting but cannot forcibly terminate its Rust thread; external CPU/memory limits and restart policy still matter.
parse_datetime accepts RFC 3339 with an explicit offset. format_date_tz applies an IANA timezone, including daylight-saving transitions. Both require clock capability even when operating on supplied timestamps. The course checks a fixed Detroit timestamp in both runtimes.
Verified 1.5.0 example
let first := pdf_render_html("<h1>Operations receipt</h1>", {"page_size": "Letter"}, {})
let second := pdf_render_html("<h1>Operations receipt</h1>", {"page_size": "Letter"}, {})
assert(first["output_sha256"] == second["output_sha256"])
assert(first["pages"] == 1)
assert(first["bytes"] > 0)
print("PDF digest repeatable")
Download this example. The repository release verifier supplies its fixtures and records success and failure diagnostics.
Verified 2026-10-22 · Official Kujo 1.9.0 release · Source contract · Download example