Tya v0.26 Specification
This document is the specification for Tya v0.26 after v0.25 bit-level operations and byte sequences.
Theme
Tya v0.26 introduces external package management.
A Tya project declares its dependencies in tya.toml, runs tya install to resolve and pin a coherent set of versions in tya.lock, and from then on all builds load the same packages reproducibly. This is the foundation for a Tya ecosystem; it does not yet open a central registry.
The design is modeled on Gleam's gleam.toml + manifest.toml and Bundler's Gemfile + Gemfile.lock, simplified for Tya's current size.
Goals
- Add a
tya.tomlmanifest as the project description file. - Add a
tya.locklockfile recording reproducible resolutions. - Add Bundler-style single-version-per-package resolution with backtracking.
- Support **git** and **path** dependency sources.
- Wire dependencies into module resolution before bundled stdlib lookup.
- Add
tya install,tya update,tya add,tya remove,tya outdated
CLI commands.
- Avoid a central registry, native dependencies, workspaces, and feature
flags in v0.26.
Included in v0.26
v0.26 includes all v0.25 behavior and adds:
tya.tomlmanifest formattya.locklockfile format- version constraint syntax (
^x.y.z,~x.y.z,>= x.y.z, < a.b.c,
exact x.y.z)
- backtracking dependency resolver
- git source (clone + checkout tag/rev) with caching
- path source (use a local directory directly)
- import resolution lookup that includes manifest dependencies
tya install/tya update/tya add/tya remove/tya outdated
Not Included in v0.26
v0.26 does not include:
- a central package registry
- workspaces / monorepos with multiple packages
- optional dependencies and feature flags
- native (C library) dependencies
- patches / overrides
- semver-aware type-identity checking
- offline-only mode
- vendoring (committing the dependency directory) as a first-class flow
tya.toml (manifest)
tya.toml lives at the project root and is human-edited.
name = "my-app"
version = "0.1.0"
description = "My Tya application"
authors = ["komagata <[email protected]>"]
license = "MIT"
[dependencies]
colorhash = "^1.2.0"
markdown = { version = ">= 0.5.0, < 1.0.0" }
fancy = { git = "https://github.com/foo/fancy", tag = "v2.1.0" }
shared = { path = "../shared" }
[dev-dependencies]
mock = "^0.3.0"
Required fields
name: package name. Must be a valid Tya identifier (lowercase letters,
digits, and _; starts with a letter).
version: SemVer 2.0.0 string (e.g."0.1.0").
Optional fields
description: short string.authors: array of strings.license: SPDX identifier or short string.[dependencies]: dict of dependency entries used for build and run.[dev-dependencies]: dict of dependency entries used only for tests.
Dependency entry forms
Short form:
colorhash = "^1.2.0"
is equivalent to
colorhash = { version = "^1.2.0" }
Long forms select a source:
# git source
fancy = { git = "https://github.com/foo/fancy", tag = "v2.1.0" }
fancy = { git = "https://github.com/foo/fancy", branch = "main" }
fancy = { git = "https://github.com/foo/fancy", rev = "abc1234" }
# local path source
shared = { path = "../shared" }
# explicit version constraint with git source
fancy = { version = "^2.1.0", git = "https://github.com/foo/fancy" }
Exactly one of tag, branch, rev is required for git when no version is given. When version is given, the resolver still locks a specific git revision in the lockfile.
Version constraint syntax
| Form | Meaning | |---|---| | "1.2.3" | exact 1.2.3 | | "^1.2.3" | >=1.2.3, <2.0.0 (caret) | | "~1.2.3" | >=1.2.3, <1.3.0 (tilde) | | ">= 1.2.3" | open lower bound | | ">= 1.2.3, < 2.0.0" | comma-separated AND |
Caret of a 0.x.y version is >=0.x.y, <0.(x+1).0 (matching Cargo's zero-major rule).
tya.lock (lockfile)
tya.lock is auto-generated by tya install and tya update. It records the exact versions chosen, their sources, and content checksums so that every machine resolves the same dependency graph.
# This file is auto-generated. Do not edit manually.
version = 1
[[package]]
name = "colorhash"
version = "1.2.5"
source = "git"
git = "https://github.com/foo/colorhash"
rev = "abc1234567890abc..."
checksum = "sha256:9f3..."
dependencies = []
[[package]]
name = "markdown"
version = "0.5.3"
source = "git"
git = "https://github.com/baz/markdown"
rev = "def4567890..."
checksum = "sha256:7a1..."
dependencies = ["colorhash"]
[[package]]
name = "shared"
version = "0.0.0"
source = "path"
path = "../shared"
checksum = ""
dependencies = []
[requirements]
colorhash = { version = "^1.2.0" }
markdown = { version = ">= 0.5.0, < 1.0.0" }
shared = { path = "../shared" }
Fields per package entry
name,version: SemVersource: one of"git","path"- For git:
git(URL) andrev(the locked commit hash);tagor
branch are recorded only if they were used to select rev.
- For path:
path(relative to the manifest) checksum:sha256:...of the unpacked source tree (path source omits
this and uses an empty string).
dependencies: array of names of other locked packages this one depends
on.
Lockfile lifecycle
tya installreadstya.tomland writes a freshtya.lockif missing,
or honors the existing lock if it already satisfies the manifest.
tya updateignores the lock and re-resolves from the manifest.tya update <pkg>re-resolves only the named package, holding others
fixed.
- The lockfile must be checked into version control. Reviewers can read
the diff to see version movement.
Resolution
The resolver is **single-version-per-package**: in any successful resolution there is exactly one chosen version of each named package across the whole dependency graph.
It uses **backtracking**:
- Build a dependency graph from
tya.toml.
2. For each unresolved package, list candidate versions sorted from highest to lowest that satisfy the union of requirements seen so far. 3. Pick the highest candidate, recursively resolve its declared dependencies. 4. On a conflict (no candidate satisfies the merged constraints), backtrack to the previous decision and try the next candidate. 5. If all options are exhausted, report an unsolvable diamond conflict with the full path of conflicting requirements.
Equality for source identity is (name, source-info):
- For git,
(name, git-url)must agree across all references; otherwise
the resolver reports a name conflict.
- For path,
(name, absolute-path)must agree.
Sources
git source
example = { git = "https://github.com/foo/example", tag = "v1.0.0" }
Behavior:
- Clone the repo into
.tya/cache/git/<host>/<owner>/<repo>(shared cache
per Tya install).
- Check out the chosen
tag,branch, orrev. - The resolver records the resolved commit
revintya.lock. - Subsequent
tya installuses the cached clone and the lockedrev.
The package directory must contain its own tya.toml at the repo root.
path source
shared = { path = "../shared" }
Behavior:
- Read the package directly from the given directory each build (no copy).
- The directory must contain
tya.tomlat its root. - Path sources do not have a checksum recorded; their content is whatever
the local directory contains at build time.
- Path sources do not participate in version constraints; their
version
field comes from the linked tya.toml.
Package layout
A package directory looks like:
<package>/
├── tya.toml # required, declares name + version + dependencies
├── src/ # required, contains public modules
│ └── <name>.tya
├── tests/ # optional
└── README.md # optional
Public modules
import <name> from a downstream package resolves to <package>/src/<name>.tya. A package with name foo must expose a top-level module foo defined in src/foo.tya. Sub-modules at src/foo/bar.tya are imported as import foo/bar (read as foo/bar).
(The slash form foo/bar is parsed but is also a future-compatibility hook; v0.26 ships only the top-level import foo form, with sub-paths deferred to a later minor version unless straightforward to include.)
Manifest of a dependency
A dependency's tya.toml is read to discover its **own** dependencies (the [dependencies] section). Dev-dependencies of dependencies are ignored; only the top-level project's dev-dependencies are loaded.
Import resolution
In v0.26, the import lookup order becomes:
- The importing file's directory.
2. **Manifest-declared dependencies (via tya.lock).** 3. Directories listed in TYA_PATH. 4. The bundled stdlib/ shipped with Tya.
Step 2 is new in v0.26. The resolver maps a package name to its source directory under .tya/packages/<name>-<version>/ (git source) or directly to the path (path source).
Existing stdlib imports such as import string continue to resolve through step 4. Stdlib names are never overridden by user manifests in v0.26.
CLI
tya install
Reads tya.toml. If tya.lock exists and satisfies the manifest, uses it as-is, downloading any missing packages. Otherwise resolves from scratch and writes a new lockfile.
tya install
Exit code is 0 on success. On unsolvable resolution it exits non-zero with a diagnostic listing the conflicting requirements.
tya update
Recomputes the lockfile from the manifest. Pass a package name to update only that package and its transitive dependencies; pass nothing to update everything.
tya update # update all
tya update colorhash # update colorhash, hold others
tya add
Adds an entry under [dependencies] in tya.toml, then runs install.
tya add colorhash # latest published version, caret constraint
tya add colorhash ^1.2.0 # explicit constraint
tya add fancy --git https://github.com/foo/fancy --tag v2.1.0
tya add shared --path ../shared
tya add --dev mock ^0.3.0 # add to [dev-dependencies]
tya remove
Removes an entry from [dependencies] (or [dev-dependencies]), updates the lockfile.
tya remove colorhash
tya outdated
Reports packages whose latest available version is greater than the currently locked version.
tya outdated
For git sources, "latest" means the highest semver tag reachable from the configured branch (or the current rev if no branch is configured). For path sources, the local manifest is consulted directly.
Cache and storage
Tya stores resolved packages in two places:
.tya/cache/git/...— shared git clones, keyed by source URL. Re-used
across projects on the same machine if the user shares a ~/.tya/cache via the TYA_CACHE environment variable.
.tya/packages/<name>-<version>/— per-project unpacked package
directories used at build time.
Both directories are safe to delete; tya install recreates them.
.tya/packages/ and .tya/cache/ should be added to .gitignore. tya.lock should be committed.
Diagnostics
v0.26 implementations should report source-oriented errors for:
- malformed
tya.toml(unknown fields, wrong types, invalid SemVer) - malformed
tya.lockor lock that does not satisfy the manifest (with a
hint to run tya install)
- unsolvable version constraints with the full conflict path
- name collisions between two source identities
- network or git failures with the offending URL
- missing
tya.tomlat a dependency's root import <name>for a name that is neither a manifest dependency nor a
stdlib module
- changes to
tya.tomlthat desync fromtya.lock(the lock is recreated
on the next tya install)
Diagnostics should mention the manifest field, the package name, the constraint that failed, and the source identity when relevant.
Examples
Minimal app
# tya.toml
name = "hello-app"
version = "0.1.0"
[dependencies]
greeting = { git = "https://github.com/example/greeting", tag = "v1.0.0" }
# main.tya
import greeting
print greeting.hello("world")
tya install
tya run main.tya
Tracked path dependency for monorepo-lite layout
project/
├── tya.toml
├── main.tya
└── lib/
└── shared/
├── tya.toml
└── src/
└── shared.tya
# project/tya.toml
name = "project"
version = "0.1.0"
[dependencies]
shared = { path = "lib/shared" }