Это руководство проводит один пакет от чистой установки Topaz через создание, разработку, статические проверки и документацию к прямому автономному запуску. Текущая идентичность продукта — Topaz 5.6.5, режим языка — topaz-5.6. Патч завершает инструментальный workflow, не изменяя грамматику v5.6.
Требования
Установите Topaz и проверьте точную идентичность продукта и языка:
curl -fsSL https://topaz.ooo/install.sh | sh
topaz version --verbose
Для native-сборки нужен Rust toolchain. Для выполнения Python-продукта нужен Python 3.11 или новее. Они не требуются для написания, проверки, контроля форматирования, тестирования и документирования пакета.
Создание пакета
В пустом рабочем каталоге создайте установленным CLI окончательный корень приложения:
topaz init --root release-inventory
Повторная команда отказывается перезаписывать scaffold. Итоговый пример имеет следующую структуру:
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.tpzПеред записью файлов ниже создайте каталоги, которых нет в начальном scaffold:
mkdir -p release-inventory/src release-inventory/data release-inventory/out
mkdir -p registry/report_slug/1.0.0/src
Замените scaffold-манифест следующим описанием пакета. Файловые capabilities разрешают чтение только под data и запись только под out; обычный выход за пределы корня и выход через symbolic link отклоняются.
[package]
name = "release_inventory"
version = "0.1.0"
language = "5.6"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.6"
report_slug = "1.0.0"
[capabilities.fs]
read = ["data"]
write = ["out"]
Entry выбирает режимы sample и негативных проверок, читает структурированные данные и записывает отчёт:
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 проверяет 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 объединяет локальный модуль, коллекции, CSV-вход, детерминированные ошибки и зависимость пакета:
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")}")
}Создайте manifest локальной registry-зависимости и её src/lib.tpz:
[package]
name = "report_slug"
version = "1.0.0"
language = "5.6"
entry = "src/lib.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.6"
[exports]
module = "src/lib.tpz"
export function slug(name: string) -> Result<string, string> {
let spaces = Regex.compile(" +")?
Ok(spaces.replaceAll(name.trim(), "-"))
}Наконец, запишите runtime-входы и создайте каталог вывода:
# 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, vendoring и проверки разработки
Скопируйте зависимость в vendor, затем удалите registry. Последующие package-команды используют только lockfile и 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 вызывает тот же formatter, что и fmt, но ничего не записывает. Редактор может запустить topaz lsp --root release-inventory: сохранённый пакет разрешает vendored-модуль, а некорректное несохранённое изменение диагностируется из overlay без изменения файла на диске.
Запустите проверенный пакет и детерминированный случай некорректных данных:
topaz run --root release-inventory --locked
topaz run --root release-inventory --locked -- --bad-sample
Успешный отчёт выглядит так:
accepted=2
Core-API:91
Python-Host:88Сборка двух продуктов
Native- и Python-продукты собираются разными командами:
topaz build --release --root release-inventory --locked --out-dir native-out
topaz build --target python --root release-inventory --locked --out-dir python-out
Native runtime-файл — native-out/target/release/program (program.exe в Windows). Python runtime-файлы — python-out/program.py и python-out/topaz_py_rt.py. Каждая сборка также создаёт topaz-artifact.json с toolchain 5.6.5, режимом topaz-5.6, target, runtime-требованиями и хешами managed files.
Скопируйте в отдельные runtime-каталоги только эти файлы, data/ и пустой доступный для записи out/. Затем удалите исходники приложения, registry, сгенерированную документацию и оба каталога сборки:
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
При отключённых сети и proxy запустите каждый продукт из его runtime-каталога, чтобы объявленные относительные корни data/ и out/ оставались действительными:
(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)
Оба продукта создают одинаковый отчёт и выходной файл без checkout Topaz, исходников приложения, registry, дерева сборки Cargo и сети. Native-продукту нужны только executable и объявленные runtime-данные; Python-продукту также нужны topaz_py_rt.py и Python 3.11+.