d6 docs

Publishable cut contract

New artifacts use .d6 source entry points. Facet continues to accept and audit .di sources and entry points for existing, immutable releases; the current layout below describes newly built cuts.

Status: artifact validation, publishing, and the HTTP read/publish service are implemented. The public registry runs at https://cuts.dilang.tech. Current behavior is in packages.md; the wire API is in registry-protocol.md.

facet check <cut-directory> now performs the first local preflight. It checks the canonical name and directory, strict version spelling, summary, nonempty license declaration, public maintainers, data-only runtime dependency ranges, and regular Cutfile, README.md, LICENSE, and lib/<name>.d6 files. It rejects unknown manifest keys. It audits the candidate runtime file tree and --files prints the sorted included file list. It does not yet verify SPDX expressions. It checks statically visible literal imports for paths that escape the cut and require_cut names missing from the manifest; dynamic imports are outside this check. Passing this check is necessary for publishing, but is not a publish operation.

Identity and layout

A cut has one canonical, case-sensitive name and one public entry point. Initially names use lowercase ASCII letters, digits, and underscores, start with a letter, and are at most 63 bytes. Reject names that differ only by ASCII case, reserved names, path separators, dots, percent encoding, Unicode lookalikes, or trailing separators. A future namespace scheme must be designed before accepting scoped names. This narrow rule makes paths and registry names unambiguous on case-insensitive filesystems.

Bundled source lives at packages/<name>/ inside d6's repo. An external cut may live at the root of its own repo. In either case, the publishable root has the same layout:

<cut root>/
  Cutfile             required for publication
  README.md               required for publication
  LICENSE                 required for publication
  lib/
    <name>.d6             required public entry point
    <name>/...            optional implementation files
  test.sh                  optional development test runner
  tests/                  optional development tests

facet check --files currently selects Cutfile, README.md, LICENSE, and regular files under lib/, bin/, and assets/. The latter two directories are optional. Other top-level files and directories, including tests, VCS files, lockfiles, build output, and installed cuts/, are excluded. A hidden, credential-like, cache, symlink, hardlink, special, oversized, non-ASCII, or case-colliding entry inside a selected runtime directory fails the check. The first format limits one file to 10 MiB, total selected bytes to 50 MiB, and selected files to 4,096. These limits can be revisited with real package needs.

facet pack <cut-directory> <output.tar> creates a deterministic, uncompressed ustar archive from exactly the checked relative file list. It prints the list and SHA-256 digest. Output must be outside the cut directory and cannot replace an existing file. An archive has regular-file entries only, sorted by relative path, followed by two zero blocks. Every entry has uid/gid 0, an empty owner/group name, and mtime 0. Its mode is 0755 when the source has any execute bit, otherwise 0644. Paths longer than 99 bytes fail until an extended-header policy is specified. These fixed fields make equal contents produce equal archive bytes across repeated packs. Explicit includes may be added later, but cannot override safety exclusions.

facet verify <archive.tar> [--sha256 <digest>] checks this exact archive format without extracting files or running cut code. It rejects unsafe paths, noncanonical headers, unexpected bytes, missing required files, and invalid release metadata. It applies the same literal-import audit as facet check, then reports the cut identity and SHA-256 digest. Supplying --sha256 requires the archive bytes to match the expected digest. A future locked install must use that expected-digest form before extraction; an unqualified verify is useful for inspecting a local archive but cannot establish that it matches a lockfile or registry record.

An installed cut occupies cuts/<name>/ within the consuming project, with cuts/<name>/lib/<name>.d6 as its entry point. Installation never writes into d6's own packages/ tree. A version is selected per name for the whole application because the compiler currently uses a flat program namespace.

Manifest

Releases published before the rename contain diamond.cut instead of Cutfile, and tools still read them. A directory or archive with both names is rejected.

The publishable Cutfile is data, using a restricted literal grammar: one Hash literal with String keys and String, Hash, Array, Boolean, or integer literal values as needed by the schema. No calls, interpolation, variable references, require, or other executable expressions. Duplicate keys and unknown top-level keys fail validation. Metadata is bounded in size. The registry reads this data, never executes it. Before publishing, facet must parse the same bytes with the same rules locally.

The first registry schema should require:

{
  "name": "greeter",
  "version": "1.2.0",
  "summary": "Small greeting helpers",
  "license": "MIT",
  "maintainers": [{"name": "Ada Lovelace", "contact": "ada@example.com"}],
  "dependencies": {"logger": "^0.4.0"}
}

name matches the root directory and entry point. version is strict SemVer 2.0.0, canonical without a leading v; build metadata needs an explicit identity policy before publication is allowed. summary is single-line plain text of 1-160 bytes. maintainers is an Array of 1-8 Hashes, each with exactly a name (1-80 bytes of printable UTF-8 without leading or trailing spaces) and a contact (an email address or an https:// URL without credentials, at most 200 bytes). Contacts must be distinct. This information is public: the registry catalog displays it. Use an address or issue tracker you intend to publish, never a credential or private channel. It is display metadata only and does not grant registry ownership or publishing rights; those come from operator-issued scoped credentials. license uses an SPDX expression or an explicit LicenseRef convention to be specified. Runtime dependencies are name-to-range mappings; no floating Git branch, arbitrary URL, or local path is allowed in a published cut's runtime graph. Optional homepage, source, documentation, and issue links are display metadata only and must never determine where facet fetches package bytes. Compatibility fields for supported d6 versions and external system libraries need a schema before packages that require them are published.

The manifest declares runtime dependencies only. Development dependencies belong in a project-local development manifest or test configuration, not in the published runtime graph. A cut must explicitly require_cut the cuts it uses; dependency declaration does not import them. The publisher validates that the manifest and archive identity agree, and rejects undeclared runtime imports where static inspection can find them.

Published library dependency ranges should cover versions actually tested, without pinning every dependency to a single release. The consuming application's Cutfile.lock records the exact resolved graph. Published library lockfiles are not used to resolve a consumer's graph.

Maintainers in existing archives

facet check, facet pack, and facet publish require maintainers. facet verify and installation still accept archives published before the field existed; facet verify --json reports "maintainers": [] for them. The registry requires maintainers when it creates a release. An identical retry of a release published earlier stays idempotent, and existing release bytes are never rewritten. To add maintainers to a published cut, publish a new version. The version 1 metadata API is unchanged, because facet 0.7.0 rejects unknown metadata keys; maintainers appear in the catalog.

Request publishing access

For the public registry, request maintainership through GitHub issues. The guide explains the required evidence, operator review, and private credential delivery. Approval is required before publishing.

Release and install invariants

  • The tuple (registry source, name, version) identifies exactly one immutable artifact and manifest. A failed or yanked release cannot be republished with different bytes under that tuple.
  • facet verifies the artifact's cryptographic digest from the lockfile before extraction. Extraction rejects absolute paths, .., escaping symlinks and hardlinks, device files, duplicate entries, and case-folding collisions.
  • Resolution uses registry metadata and never runs cut code. Installation does not run lifecycle scripts. Loading a cut executes its library code only when the application requests it.
  • New resolution skips yanked releases. An already locked, verified release remains installable unless an exceptional security or legal takedown removes it. Such a removal must produce an explicit error, not silent substitution.
  • Publishing requires ownership of the name and authenticated intent. Owner changes and release actions are audited. A publisher can deprecate or yank a bad release; ordinary deletion is not a package maintenance operation.
  • The client pins the registry source for each name. If two requesters mean different sources or incompatible ranges, resolution fails with both paths through the dependency graph. It does not fall back to another registry.

Compatibility with the current implementation

Current facet accepts Git dependencies shaped like {"git": URL, "version": RANGE} and registry dependencies shaped like {"registry": HTTPS_URL, "version": RANGE}. Git versions are derived from tags; registry versions and transitive dependencies come from the configured version 1 metadata API. Both facet and require_cut parse metadata without executing it. All bundled cuts now have release metadata and license files, declare their runtime dependencies, and pass local archive checks. Dependent cuts load their dependencies through require_cut. tools/install_local_cuts.sh can stage verified local artifacts. facet install can install an exact registry artifact from a committed lock record, and facet update resolves registry metadata. facet publish and the registry service are implemented and the public service is live. New source versions in this repository are not necessarily published; query the registry for available versions.

The migration now uses a dedicated data-only parser. Preserve reading existing literal manifests and Git source specs. Git lock entries now declare "source": "git"; old entries without a source remain readable, while unknown source types fail. Registry source specs and lockfile digests remain distinct from old Git locks. The flat cuts/<name>/ compatibility fallback can remain for manually installed legacy cuts; published artifacts use lib/.

facet verify ... --json emits one machine-readable JSON object on success, with protocol, name, version, SHA-256, size, and dependency ranges derived from the verified archive. It supports --sha256 in either option order and emits no success JSON for an invalid archive. Registry publishing uses this output instead of parsing human-readable diagnostics or evaluating the manifest.

Edit this page on GitHub