Outcome: assemble a locked two-module package, run one selected test and the package entry, then execute its Python product without the .tpz project or Topaz compiler.
Prerequisite: complete Failures and Resources, install topaz, and have Python 3.11 or newer available.
Start with the ordinary scaffold
From the directory that should contain your project, run:
topaz init --root study-plan
cd study-plan
init refuses to overwrite an existing project. It creates topaz.toml and src/main.tpz; it does not create a lockfile or test. Replace the entry source and add the module and selected test so the tree becomes:
study-plan/
├── topaz.toml
├── src/
│ ├── main.tpz
│ └── plan.tpz
└── tests/
└── plan.tpzThe scaffold manifest is:
[package]
name = "study-plan"
version = "0.1.0"
language = "5.10"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.10"
Create the two modules
src/plan.tpz owns the domain and reusable operations:
export record StudyTask {
title: string,
minutes: int,
done: bool = false,
}
function parseMinutes(text: string) -> Result<int, string> {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
export function makeTask(
title: string,
minuteText: string,
done: bool = false,
) -> Result<StudyTask, string> {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes, done: done })
}
export function summarize(tasks: Array<StudyTask>) -> string {
let mut remaining = 0
for task in tasks {
if !task.done {
remaining = remaining + task.minutes
}
}
"tasks={tasks.length}, remaining={remaining} minutes"
}Only the names marked export are visible to another module. parseMinutes remains a private implementation detail.
src/main.tpz imports those visible names and supplies the package entry:
import src.plan { StudyTask, makeTask, summarize }
export function main(args: Array<string>, stdin: string) -> Result<int, string> {
let tasks: Array<StudyTask> = [
makeTask("Run first program", "10", true)?,
makeTask("Build application", "25", false)?,
]
print(summarize(tasks))
Ok(0)
}The explicit main receives command-line arguments and standard input, then returns either an exit code or an error string. This application needs neither input, but the signature keeps the product boundary visible.
Add tests/plan.tpz:
import src.plan { StudyTask, makeTask, summarize }
match makeTask("Read syntax map", "15") {
case Ok(task) => assert(task.minutes == 15, "valid minutes")
case Err(message) => assert(false, message)
}
match makeTask("Read syntax map", "later") {
case Ok(_) => assert(false, "invalid minutes were accepted")
case Err(message) => assert(
message == "minutes must be a positive integer",
"invalid minutes",
)
}
let tasks: Array<StudyTask> = [
StudyTask { title: "Run first program", minutes: 10, done: true },
StudyTask { title: "Build application", minutes: 25 },
]
assert(
summarize(tasks) == "tasks=2, remaining=25 minutes",
"summary",
)The isolated TestHost supplies the canonical assert(...) function for tests. The file checks valid input, invalid input, and the final summary without changing production code.
Lock, format, check, test, and run
Create and inspect the dependency lock:
topaz lock --root .
The generated topaz.lock is:
[[package]]
name = "study-plan"
version = "0.1.0"
source = "root"
manifest_hash = "sha256:780ff549a9fb9b09d27c62df8ce48e70c1fc7b33d6d429e6bb826685f42e7615"
Now run the local quality loop in order:
topaz fmt --check --root .
topaz check --root . --locked
topaz test tests/plan.tpz --root . --locked
fmt --check reports drift without rewriting files, check validates the resolved unit, and the explicit path selects one test entry. The test output is:
tests/plan.tpz: test-okRun the package entry:
topaz run --root . --locked
It prints:
tasks=2, remaining=25 minutesBuild and run without the Topaz project
The scaffold’s default target is native, but an explicit command can select another target. Build the course’s Python product:
topaz build --target python --root . --locked --out-dir ../study-plan-product
Python is deliberate here: it needs Python 3.11 or newer at runtime, but it does not add a Rust installation to the beginner path. The managed product directory contains exactly:
study-plan-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── program.py
├── topaz-artifact.json
└── topaz_py_rt.pyMove the entire Topaz project out of the way, then run from the product directory:
cd ..
mv study-plan source-unavailable
cd study-plan-product
python3 program.py
In PowerShell, replace the move command with Rename-Item study-plan source-unavailable, and use python program.py. The result is byte-for-byte the package output:
tasks=2, remaining=25 minutesThe product still contains generated Python and its runtime support; “without source” here means it contains no .tpz files and does not need the Topaz project, compiler, registry, or build workspace.
Keep the boundaries straight
- A file is one
.tpzsource file. - A module is the namespace a file exposes through
export. - A unit is the set of Topaz files resolved from the entry and its imports.
- A package records product identity and locked dependencies in
topaz.tomlandtopaz.lock. - A target chooses the product shape, such as Python.
- An artifact is the managed build result you deliver and run.
Decision: source loop or product boundary?
Use check, test, and run while changing the Topaz project because they give fast source-level feedback. Use build when you need the exact deliverable and then test that deliverable outside the project. Running only inside the source tree proves the program, not the delivery boundary.
Try this
Change the second task’s minute text in src/main.tpz from "25" to "30". Which expectations must change before the complete loop passes?
Answer
The package and Python runs should print tasks=2, remaining=30 minutes. The selected test constructs its own 25-minute task, so its existing summary remains correct and still passes. This separation shows that the test owns its fixture rather than silently depending on main.
Ready to continue when
You can explain all six boundaries above, regenerate and enforce the lockfile, run fmt --check, check, the selected test, and run, and execute the Python product after the .tpz project is unavailable.
Use Syntax at a Glance as your map, then deepen the parts you need in Modules & Visibility, Application Loop, and Artifacts & Deployment.