Skip to content

Packages And Distribution

Zynx implements a deliberately narrow package distribution model. Builds use local source, exact git dependencies, and lockfiles. Several conventional package features are intentionally absent (see Not Implemented).

Quick Path

Create a local package and validate its manifest:

bash
./zynx init --name my_project
./zynx package check

Run the default executable target:

bash
./zynx run

For git dependencies, declare the dependency in zynx.json, then write the selected exact revision to zynx.lock:

bash
./zynx update

Use ./zynx fetch to restore missing package-cache checkouts from an existing lockfile without selecting new revisions.

Current Package Surface

  • local project packages with zynx.json
  • exact git dependencies declared in zynx.json
  • optional manifest-pinned git revisions with rev
  • zynx.lock reuse for deterministic dependency selection
  • package-cache restoration from the lockfile with zynx fetch
  • dependency updates with zynx update or zynx update <package>
  • deterministic module identity and duplicate-module rejection
  • manifest-declared binary resources for @embed("logical-name")
  • .zxm bundle metadata validation for module name, target, ABI, and bundle kind

Not Implemented

  • a central package registry
  • semver solving and version ranges such as ^1.2.0, ~1.2, or >=1
  • signed package verification
  • publishing commands
  • an offline vendor-cache format
  • dependency declarations by registry package name alone
  • mirror selection
  • transitive dependency solving beyond the exact locked package graph

Unsupported dependency keys are rejected. Entries may contain only git and optional rev.

Accepted zynx-crypto Boundary

RFC 0019 moves every cryptographic algorithm, construction, protocol format, and algorithm-specific type out of std into the official independently released zynx-crypto package. It is an ordinary static dependency, not a privileged SDK import.

Applications select it using the same exact git revision and lockfile content identity as any other dependency. A compiler or SDK update does not silently change the selected crypto revision, and a crypto security update does not require a language release. Published sealed.vN formats are immutable.

The package has not replaced the current std experiment yet. RFC 0020 fixes its typed-value contract: software secrets are construction-specific nominal SecretKey<A>-style owners of concrete ^A and one private non-reallocating payload whose length is fixed once during construction; allocation consumes the explicit allocator capability and uses an *_alloc name; moves transfer ^A, one pointer, and one cleanup/free obligation; algorithms use shared loans; deliberate byte export is caller-buffer export_into; cleanup wipes the complete backing before free on every defined lexical, checked-error, partial-init, and replacement path.

Secrets have no default/sentinel/uninitialized state, Copy, blanket Clone, ordinary public bytes(), automatic serialization/format/hash/order/equality, or unconditional Send/Sync. Reviewed types may expose duplicate_alloc or constant-time equal_secret. Fixed-size export uses an exact array; runtime-size export validates exact output size before writing and never returns a partial secret copy. Small fixed public Nonce, PublicKey, Signature, Digest, Salt, and Tag values stay nominal inline Copy + Send + Sync; nonce uniqueness remains protocol policy and Tag has no ordinary equality, so tag decisions use verify. Provider-backed keys use provider-specific nominal handles. There is no universal language Secret<T> or compiler magic, and hardened locked storage is a future pay-for-use facility.

The security-support window, release cadence, audit policy, and emergency- update policy remain follow-up package-governance tasks.

Minimal Project Manifest

Create a local project manifest in the current directory with:

bash
./zynx init
./zynx init --name my_project

zynx init writes zynx.json and creates src/main.zx when it is missing. The default package name is derived from the current directory and normalized to ASCII package-name characters. --name supplies the package name explicitly. If zynx.json already exists, zynx init fails instead of rewriting it. zynx init does not create zynx.lock for a dependency-free project.

json
{
  "format": "zynx-project",
  "version": 1,
  "package": {
    "name": "app",
    "version": "0.1.0"
  },
  "targets": {
    "app": {
      "kind": "executable",
      "main": "src/main.zx"
    }
  },
  "default_target": "app",
  "build": {
    "target": "native",
    "profile": "debug"
  },
  "resources": {
    "protocol-fixture": "resources/protocol.bin",
    "ui/logo": "assets/logo.bin"
  },
  "dependencies": {
    "util": {
      "git": "https://example.invalid/util.git",
      "rev": "0123456789abcdef0123456789abcdef01234567"
    }
  }
}

package.version is metadata only in the current language. It does not participate in dependency selection.

Manifest fields used by the current package workflow:

FieldPurpose
formatMust be "zynx-project".
versionManifest schema version; the current value is 1.
package.nameLocal package name and dependency identity.
package.versionMetadata only; it is not used for dependency selection.
targetsNamed build targets, such as executables.
default_targetTarget used by commands such as zynx run.
build.targetDefault build target, commonly "native".
build.profileDefault build profile, commonly "debug".
resourcesLogical resource names mapped to package-root-relative regular files.
dependenciesExact git dependencies by local package name.

Host Capability Requirements

An executable's composition-root source declares each initial filesystem and network authority it requires:

Zynx
// RFC 0066/0068/0069/0070/0071/0072/0073/0074 target; implementation pending.
@host let project: fs.Dir in _;
@host let outbound: net.Network in _;

These requirements are inferred from the selected target root and recorded as ordered private target metadata. They are not exports, ordinary globals, argv, environment variables, or transitive dependency requirements. A dependency cannot declare a host loan on behalf of its importer, and an unused declaration is rejected, so the recorded set is exact.

The host must satisfy every declaration with one correctly typed grant before entering main; missing, duplicate, stale, unauthorized, or unrepresentable bindings fail target startup atomically. RFC 0068 fixes one deployment-only configuration boundary: one explicitly named zynx-launch document. Versions 1, 2, 3, and 4 are complete frozen schemas; the document remains separate from zynx.json, lockfiles, package resources, and importable .zxm metadata.

json
{
  "format": "zynx-launch",
  "version": 1,
  "requirements_digest": "blake3:<generated-requirement-digest>",
  "grants": {
    "project": {
      "kind": "directory",
      "source": { "kind": "path", "path": "./project" },
      "policy": {
        "rights": [
          "duplicate",
          "open_directory",
          "entries",
          "walk",
          "metadata",
          "open_file.read_at"
        ]
      }
    },
    "outbound": {
      "kind": "network",
      "policy": {
        "resolve": {
          "kind": "names",
          "names": ["api.example.test"]
        },
        "tcp_connect": [
          {
            "prefix": "203.0.113.0/24",
            "ports": { "kind": "one", "port": 443 }
          }
        ],
        "tcp_listen": [],
        "udp_bind": [],
        "udp_connect": [],
        "udp_send_to": []
      }
    }
  }
}

RFC 0069 completes the closed policy bodies. Every grant must contain policy; omission is invalid, while an explicit empty directory rights list or empty network operation list denies that authority. Unknown fields, rights, selector kinds, duplicate names, and duplicate exact rules are errors. There is no directory all/read/write preset and no implicit unrestricted Network.

RFC 0070 leaves version 1 byte-for-byte semantic and extends only version 2 directory policy with these five rights:

text
create_directory
remove.non_directory
remove.empty_directory
rename.source
rename.destination_replace

All nine version-1 directory rights remain unchanged in version 2, and the complete Network schema is identical between versions. A v2 document otherwise has the same envelope and exact grant matching shown above, with "version": 2. No other operation, selector, default, or broad mutation right is introduced.

RFC 0071 does not change either launch schema, version, or rights registry. Its exact fs.OpenMode { access, disposition } decomposes into the four existing version-1 file-open rights, identically under version 2. Read and write access request their corresponding data rights; create-capable dispositions request open_file.create; truncate-capable dispositions request both open_file.truncate and open_file.write_at. The complete sum is authorized before the first path lookup, so OpenOrCreate requires create authority even when the entry exists and CreateOrTruncate requires create authority even when the entry is absent. A successful file retains only the read/write data access selected by the mode. RFC 0072 separately permits it to carry the independent open_file.sync capability from its source directory; it inherits no other directory or open right.

RFC 0072 freezes version 2 and introduces version 3 by copying all fourteen v2 directory rights and the complete Network schema unchanged, then adding exactly:

text
open_file.sync
sync_directory

open_file.sync is an independent capability retained by files opened through that grant. It does not imply read_at, write_at, create, or truncate, and none of those rights implies synchronization. sync_directory is likewise independent of directory creation, removal, and either rename role. Versions 1 and 2 reject both names. Version 3 otherwise has the same envelope and exact grant matching, with "version": 3; its requirement digest domain and Network policy are unchanged.

Grant acquisition fails before main if the selected adapter cannot implement a requested exact barrier. It never substitutes a no-op or best effort. This means a standard Windows or portable WASI plan requesting sync_directory is unrepresentable; a certified target adapter may expose only the barriers it can actually uphold. Inability discovered only from a concrete mounted filesystem or resource is reported later as io.IOError.Unsupported.

RFC 0073 freezes version 3 and introduces version 4 by copying all sixteen v3 directory rights and the complete Network schema unchanged, then adding exactly:

text
open_file.append

Every AppendMode requires that right. ReadAppend additionally requires open_file.read_at; create-capable dispositions additionally require open_file.create; truncate-capable dispositions additionally require open_file.truncate, but not open_file.write_at. Append authority never grants arbitrary-offset writes. If open_file.sync is also present, the opened AppendFile independently carries it for its explicit full-integrity barrier. Versions 1 through 3 reject open_file.append; version 4 otherwise retains the same envelope, digest domain, and exact matching rules.

The selected adapter must certify atomic EOF placement for every append owner. A target known to lack that operation rejects the v4 grant before main; a weak, remote, or otherwise unsupported concrete filesystem discovered later returns io.IOError.Unsupported. The host must not emulate append as a size query followed by an offset write, and it never silently weakens the operation.

RFC 0074 adds no publication right and did not itself add zynx-launch version 5. Durable single-file publication uses the existing exact rights open_file.write_at, open_file.create, open_file.sync, rename.source, rename.destination_replace, and sync_directory. Version 3 can authorize that complete composition. RFC 0076 later adds version 5 by copying the entire version-4 Directory and Network policy unchanged and adding only the unrelated MMIO kind. Explicit cleanup of an abandoned caller-chosen staging name additionally requires remove.non_directory, but cleanup is application policy rather than part of a publication grant.

There is no publish, write_atomic, temporary-name, generated-name, cleanup-on-drop, rollback, or recovery right. A launch grant conveys operation authority, not exclusive access to directory names and not a hidden identity lock across close and path-based rename. The program must coordinate competing actors and synchronize the exact directory whose direct-child entry changed; synchronizing only a higher ancestor is insufficient.

Directory rights are exact operation capabilities. Network policy independently authorizes raw-name resolution and numeric endpoint effects; permitting a name never permits connecting to its result. Broad authority remains explicit: resolve.kind = "any", family-specific 0.0.0.0/0 or ::/0, and tagged ports.kind = "any" are visible grants rather than defaults.

The document is supplied through the only standard CLI path:

text
zynx run --grants deployment.json -- program-args...
zynx launch --grants deployment.json app -- program-args...

There is no grant-file auto-discovery, CLI/env override, inline grant flag, include, overlay, profile merge, or last-wins rule. build does not read the document, and program arguments after -- never contain its path or contents. The requirements_digest covers the ordered source names and canonical kinds recorded by the selected executable artifact. Grant-map order is irrelevant; validation, acquisition, rollback, publication, and teardown use artifact declaration order. The digest does not cover the launch-manifest version, concrete policies, paths, or rights, and versions 2, 3, and 4 do not introduce another digest domain.

The grant set must match exactly before acquisition. The launcher then acquires all backing owners, publishes every binding atomically, enters main, waits for quiescence, and tears down in reverse order. An artifact with requirements cannot be executed directly without a typed launch plan; an artifact with no requirements still needs none.

A directory source uses either an explicit path, resolved once relative to the already-open launch-document directory when not absolute, or the explicit { "kind": "process_start" } source. No binding name has magic meaning: project may receive the process-start directory and a binding named start receives nothing unless its grant says so. The acquired Dir is handle-backed and is never reopened from remembered text. WASI and embedded hosts build the same typed per-instance plan directly rather than gaining a registry or ambient fallback.

Embedded Resources

@embed embeds exact binary bytes from a resource declared in the defining package's resources map:

Zynx
fn check_fixture() {
    let fixture: [u8] = @embed("protocol-fixture");
    _ = fixture;
}

The argument is exactly one string literal containing a logical resource name, not a path. Names are nonempty, case-sensitive manifest keys. / may organize names such as ui/logo, but it has no filesystem meaning. A dependency module always resolves names in its own package manifest, never in the importing package.

Each mapped file path must be normalized and package-root-relative. Absolute paths, empty components, . or .., directories, root escapes, and symlink escapes are rejected. A symlink may target a regular file only when its final resolved location remains inside the package root.

The build snapshots declared resources before compilation and includes each logical name, byte length, and content digest in the build graph, cache key, and artifact identity. The compiler receives those bytes as immutable inputs; it does not reopen paths during analysis, constant evaluation, or code generation. The result is an immutable program-lifetime [u8] view over exact read-only target data, with no allocation, copy, or drop.

@embed is an ordinary frontend literal lowered from that build input; it is not a constant-evaluator value. It is rejected in declaration-level const, const-generic expressions, const fn, and immediately invoked closures used for constant evaluation.

There is no path fallback, embed_text, dynamic name, glob, directory embed, compression mode, or implicit text decoding. Existing path-based calls must declare a logical name in resources and keep the builtin spelling @embed:

json
{
  "resources": {
    "logo": "assets/logo.bin"
  }
}
Zynx
fn draw_logo() {
    let logo: [u8] = @embed("logo");
    _ = logo;
}

Lockfile Shape

zynx.lock records the exact dependency graph selected for a build:

json
{
  "format": "zynx-lock",
  "version": 1,
  "packages": {
    "util": {
      "git": "https://example.invalid/util.git",
      "rev": "0123456789abcdef0123456789abcdef01234567",
      "path": "git/util/0123456789abcdef0123456789abcdef01234567"
    }
  }
}

The lockfile is part of the build input. A normal build reuses a matching lockfile entry for the same dependency name and git URL. If the package cache is missing but the lockfile names a valid git revision, zynx fetch restores the checkout into the cache.

Reproducing Builds

To reproduce a build, keep these inputs fixed:

  • project source
  • zynx.json
  • zynx.lock
  • package cache entries named by zynx.lock
  • active SDK and standard library
  • explicit --module-path roots, if any

Fully offline reproduction requires the locked package cache and SDK/stdlib inputs to already be present. Zynx does not define an offline vendor archive format or a registry mirror.

Common Commands

CommandBehavior
./zynx init --name my_projectCreate zynx.json and src/main.zx when missing.
./zynx package checkValidate package state without fetching or changing zynx.lock.
./zynx fetchRestore missing locked git checkouts into the package cache.
./zynx updateRefresh every unlocked git dependency to remote HEAD.
./zynx update <package>Refresh one unlocked git dependency.
./zynx runBuild and run the default executable target.
./zynx run --grants <file> -- <args...>Accepted RFC 0068–0074 target: build and run through one explicit per-instance grant plan with mandatory versioned closed policies; implementation pending.
./zynx launch --grants <file> <artifact> -- <args...>Accepted RFC 0068–0074 target: launch an existing artifact through the same grant adapter; implementation pending.

Updating Dependencies

Check package state without modifying zynx.lock or fetching dependencies:

bash
./zynx package check

package check validates zynx.json, reads zynx.lock when present, reports missing or stale dependency lock entries, and reports invalid existing package cache checkouts. It does not contact git remotes and does not create or update the lockfile.

Run:

bash
./zynx update

to refresh every unlocked git dependency to its remote HEAD, or:

bash
./zynx update util

to refresh one dependency. The selected exact revision is written back to zynx.lock.

Manifest dependencies with rev are pinned to that revision. Changing a pinned revision is a source edit to zynx.json, followed by a normal build or fetch.

Module Identity

Imports use dotted module identities. A package module must provide the same identity that the source imports. Duplicate non-standard module identities across explicit module paths or locked dependency packages are rejected instead of being resolved by path order. A local source file next to the importer may intentionally shadow package modules.

Bundle Validation

Static .zxm bundles may be imported only when their embedded metadata matches the requested module identity, active target, ABI, and supported bundle kind. Dynamic bundles and runtime loading are not implemented.

Next Steps

  • Use Modules And Visibility for import lookup, package module identity, and visibility rules.
  • Use the Build Reference for SDK and standard-library build inputs that also affect reproducibility.

Released under the MIT License.