This guide takes one package from a clean Topaz installation through creation, authoring, static gates, documentation, and direct offline native, Python, and interactive Web execution. It uses the current public product identity Topaz 5.7.0 with language mode topaz-5.7; the release expands the tool workflow, not the v5.7 grammar.
Prerequisites
Install Topaz and confirm the exact product and language identity:
curl -fsSL https://topaz.ooo/install.sh | sh
topaz version --verbose
Native builds require a Rust toolchain. Python products require Python 3.11 or newer at runtime. Neither toolchain is needed merely to author, check, format, test, or document the package.
Create the package
Start from an empty working directory and let the installed CLI create the final application root:
topaz init --root release-inventory
Running the command again refuses to overwrite the scaffold. The completed example has this layout:
release-inventory/
topaz.toml
src/main.tpz
src/policy.tpz
src/report.tpz
data/releases.csv
data/policy.toml
out/report.txt
registry/
report_slug/1.0.0/topaz.toml
report_slug/1.0.0/src/lib.tpzCreate the directories that are not part of the initial scaffold before writing the files below:
mkdir -p release-inventory/src release-inventory/data release-inventory/out
mkdir -p registry/report_slug/1.0.0/src
Replace the scaffold manifest with the following package declaration. The filesystem capabilities allow reads only under data and writes only under out; ordinary path traversal and symlink escapes are rejected.
[package]
name = "release_inventory"
version = "0.1.0"
language = "5.7"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.7"
report_slug = "1.0.0"
[capabilities.fs]
read = ["data"]
write = ["out"]
The entry selects sample and negative-test modes, reads structured input, and writes the report:
import src.report { buildReport }
import std.fs
const SAMPLE_CSV = "name,status,score\nCore API,ready,91\nDraft Tool,draft,99\nCLI Pack,ready,84"
const SAMPLE_POLICY = "[policy]\nrequired_status = \"ready\"\nminimum_score = 85"
function renderSample(csv: string) -> Result<int, string> {
let report = buildReport(csv, SAMPLE_POLICY)?
print(report)
Ok(0)
}
function hasArg(args: Array<string>, expected: string) -> bool {
match args.indexOf(expected) {
case Some(_) => true
case None => false
}
}
export function main(args: Array<string>, stdin: string) -> Result<int, string> {
if hasArg(args, "--sample") {
return renderSample(SAMPLE_CSV)
}
if hasArg(args, "--bad-sample") {
return renderSample("name,status,score\nBroken Row,ready,nope")
}
if hasArg(args, "--probe-denied") {
let secret = fs.readText("secret.txt")?
print(secret)
return Ok(0)
}
if hasArg(args, "--probe-symlink") {
let secret = fs.readText("data/escape-link")?
print(secret)
return Ok(0)
}
let inventory = fs.readText("data/releases.csv")?
let policy = fs.readText("data/policy.toml")?
let report = buildReport(inventory, policy)?
fs.writeText("out/report.txt", "{report}\n")?
print(report)
Ok(0)
}src/policy.tpz validates the TOML policy:
export type Policy = { requiredStatus: string, minimumScore: int }
function field(obj: JSONValue, name: string) -> Result<JSONValue, string> {
match obj.get(name) {
case Some(value) => Ok(value)
case None => Err("missing policy.{name}")
}
}
export function parsePolicy(text: string) -> Result<Policy, string> {
let document = TOML.toJson(TOML.parse(text)?)
let policy = field(document, "policy")?
let requiredValue = field(policy, "required_status")?
let minimumValue = field(policy, "minimum_score")?
let required = requiredValue.asString() ?? ""
let minimum = minimumValue.asInt() ?? -1
if required.byteLength() == 0 {
return Err("policy.required_status must not be empty")
}
if minimum < 0 {
return Err("policy.minimum_score must be a non-negative int")
}
Ok({ requiredStatus: required, minimumScore: minimum })
}src/report.tpz combines the local module, collections, CSV input, deterministic errors, and the package dependency:
import report_slug { slug }
import src.policy { Policy, parsePolicy }
function cell(row: Map<string, string>, name: string) -> Result<string, string> {
match row.get(name) {
case Some(value) => Ok(value)
case None => Err("missing CSV column {name}")
}
}
function acceptedLine(row: Map<string, string>, policy: Policy) -> Result<Option<string>, string> {
let name = cell(row, "name")?
let status = cell(row, "status")?
let scoreText = cell(row, "score")?
let score = match toInt(scoreText) {
case Some(value) => value
case None => return Err("invalid score `{scoreText}` for `{name}`")
}
if status != policy.requiredStatus || score < policy.minimumScore {
return Ok(None)
}
Ok(Some("{slug(name)?}:{score}"))
}
export function buildReport(csvText: string, policyText: string) -> Result<string, string> {
let policy = parsePolicy(policyText)?
let rows = CSV.parseWithHeader(csvText)?
let mut accepted: Array<string> = []
for row in rows {
match acceptedLine(row, policy)? {
case Some(line) => accepted.push(line)
case None => ()
}
}
let lines = accepted.sorted()
Ok("accepted={lines.length}\n{lines.join("\n")}")
}Create the local registry dependency with this manifest and src/lib.tpz:
[package]
name = "report_slug"
version = "1.0.0"
language = "5.7"
entry = "src/lib.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.7"
[exports]
module = "src/lib.tpz"
export function slug(name: string) -> Result<string, string> {
let spaces = Regex.compile(" +")?
Ok(spaces.replaceAll(name.trim(), "-"))
}Finally, write the runtime inputs and create the output directory:
# data/releases.csv
name,status,score
Core API,ready,91
CLI Pack,ready,84
Python Host,ready,88
Draft Tool,draft,99
# data/policy.toml
[policy]
required_status = "ready"
minimum_score = 85Lock, vendor, and run authoring gates
Vendor the dependency, then remove the registry to prove subsequent package operations use only the lockfile and vendored bytes:
topaz vendor --root release-inventory --from registry
rm -rf registry
topaz check --root release-inventory --locked
topaz fmt --check --root release-inventory
topaz test --root release-inventory --locked -- --sample
topaz doc --root release-inventory --locked --out-dir docs-out
fmt --check invokes the same formatter as fmt and writes nothing. An editor can start topaz lsp --root release-inventory; saved package diagnostics resolve the vendored module, while an invalid unsaved edit is diagnosed from the overlay without changing the file on disk.
Run the checked package and its deterministic malformed-input case:
topaz run --root release-inventory --locked
topaz run --root release-inventory --locked -- --bad-sample
The successful report is:
accepted=2
Core-API:91
Python-Host:88Build both products
Native and Python products use distinct commands:
topaz build --release --root release-inventory --locked --out-dir native-out
topaz build --target python --root release-inventory --locked --out-dir python-out
The native runtime deliverable is native-out/target/release/program (program.exe on Windows). The Python runtime deliverables are python-out/program.py and python-out/topaz_py_rt.py. Each build also writes topaz-artifact.json, which records the 5.7.0 toolchain, topaz-5.7 language mode, target, runtime requirements, and managed-file hashes.
Copy only those runtime deliverables plus data/ and an empty writable out/ into separate runtime directories. Then delete the application source, the registry, generated documentation, and both build directories:
mkdir -p native-runtime/data native-runtime/out
mkdir -p python-runtime/data python-runtime/out
cp native-out/target/release/program native-runtime/program
cp python-out/program.py python-out/topaz_py_rt.py python-runtime/
cp release-inventory/data/* native-runtime/data/
cp release-inventory/data/* python-runtime/data/
rm -rf release-inventory registry docs-out native-out python-out
With network and proxies disabled, run each product from its own runtime directory so its declared relative data/ and out/ roots remain valid:
(cd native-runtime && env HTTP_PROXY=http://127.0.0.1:9 HTTPS_PROXY=http://127.0.0.1:9 ALL_PROXY=http://127.0.0.1:9 NO_PROXY= CARGO_NET_OFFLINE=true ./program)
(cd python-runtime && env HTTP_PROXY=http://127.0.0.1:9 HTTPS_PROXY=http://127.0.0.1:9 ALL_PROXY=http://127.0.0.1:9 NO_PROXY= CARGO_NET_OFFLINE=true python3 program.py)
Both products produce the same report and output file without the Topaz checkout, application source, registry, Cargo build tree, or network. The native product needs only its executable and declared runtime data; the Python product additionally needs topaz_py_rt.py and Python 3.11+.
Web Application loop
Topaz 5.7.0 provides the checked Web Application loop while retaining language mode topaz-5.7. A normal installation runs this complete loop:
topaz init --target web-app --root hello-web
topaz check --root hello-web
topaz fmt --check --root hello-web
topaz test hello-web/tests/app.tpz --root hello-web
topaz dev --root hello-web --port 8000
topaz build --root hello-web --out-dir web-product
The scaffold fixes [build].target = "web-app", creates src/main.tpz, tests/app.tpz, and styles/app.css, and declares only normalized styles and assets under [web]. Package-mode build therefore needs no explicit target. The entry exports exactly init() -> AppStep<Model, Msg>, update(Model, Msg, BrowserEvent) -> AppStep<Model, Msg>, and view(Model) -> Html<Msg>; the checker rejects any lifecycle type disagreement before generating WASM.
topaz dev binds only to loopback, rebuilds watched package inputs, keeps the last good product after a failed edit, and reloads after a successful one. It is not a production server. The final web-product/ is a managed static product containing index.html, the safe topaz-app.js host, the raw checked Web facade and WASM, declared styles/assets, licenses, notices, and topaz-artifact.json. Copy only that directory to a fresh static root and remove hello-web; serving the copied directory requires neither package source, Topaz, a registry, npm, a CDN, nor the development process.
The generated application product is distinct from both raw web/web-worker embedding and the compiler Playground. The host builds DOM nodes without innerHTML, passes bounded browser facts to Topaz, and stops in a text-only failure state on unsafe trees, URLs, attributes, malformed ABI values, faults, or exhausted command budgets.
Choose raw Web when an existing JavaScript shell owns the UI or Worker protocol and calls selected Topaz exports. Choose Web App when Topaz owns the model, messages, update, and view and you want the complete managed static product. Data Lens develops the second path as a multi-module local-first application.