@ship.zone/ci-compiler
@ship.zone/ci-compiler is the reference compiler of the ship.zone CI standard, @ship.zone/ci-spec. It owns the deterministic parts of the standard that coordinators and runners share: RFC 8785 canonical JSON and digests, the specification version rule, the strict reading and schema check of ci_actions.yml, validation of compiled runner jobs, and candidate image index assembly. The workflow rules and run compilation follow in later releases.
Every function is pure. The only I/O is loading the normative JSON Schemas from the installed @ship.zone/ci-spec once, when the module loads; no function reads a clock, randomness, the environment, the filesystem, or the network, and equal inputs give byte-equal outputs.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
pnpm add @ship.zone/ci-compiler
Specification Version
The compiler implements exactly one specification version: the version of the @ship.zone/ci-spec it pins exactly. A coordinator or runner that uses the compiler pins the same @ship.zone/ci-spec version, so the version it negotiates is the version it compiles and checks against.
import { acceptCiSpec, implementedCiSpecVersion, lowerCiSpec } from '@ship.zone/ci-compiler';
implementedCiSpecVersion; // '2.1.1'
acceptCiSpec(implementedCiSpecVersion, '2.0.0'); // 'accepted'
acceptCiSpec(implementedCiSpecVersion, '2.2.0'); // 'newer'
acceptCiSpec(implementedCiSpecVersion, '1.2.0'); // 'major-mismatch'
acceptCiSpec(implementedCiSpecVersion, '2.0'); // 'malformed'
lowerCiSpec('2.1.0', '2.0.5'); // '2.0.5'
acceptCiSpec and lowerCiSpec throw CiCompilerUsageError for a version that must be canonical and is not; lowerCiSpec also throws for versions of different major versions, which never form a session.
Canonical JSON and Digests
import {
canonicalizeCiJson,
digestCiCompiledPlan,
digestCiJson,
projectCiCompiledPlan,
sha256CiHex,
} from '@ship.zone/ci-compiler';
canonicalizeCiJson({ b: 1, a: [true, null] }); // '{"a":[true,null],"b":1}'
digestCiJson(requestBody); // canonicalRequestDigest: lowercase SHA-256 of the RFC 8785 bytes
projectCiCompiledPlan(job); // the job without compiledPlanDigest, every secrets value replaced by true
digestCiCompiledPlan(job); // compiledPlanDigest
sha256CiHex(bytes); // lowercase hex SHA-256 of bytes or of a UTF-8 string
Canonicalization throws CiCanonicalJsonError for a value without an RFC 8785 serialization: a lone UTF-16 surrogate in a string or member name, a non-finite number, a value that is not JSON, an array with holes, or a cycle.
Workflow Check
checkCiWorkflow reads ci_actions.yml bytes as a coordinator reads them from the source commit and checks the first four layers of ci-actions, Compilation Failures: source, yaml, version, and schema. It returns the typed document or the failure of the first layer with a violation:
import { checkCiWorkflow } from '@ship.zone/ci-compiler';
const result = checkCiWorkflow(bytes);
if (result.ok) {
result.value.spec; // '2.0.0'
result.value.workflowDigest; // lowercase hex SHA-256 of the bytes as read
result.value.document; // ICiWorkflow
result.value.mappingKeyOrder.get('/jobs/test/matrix'); // matrix dimensions in declaration order
} else {
// result.failure: { code, message, location?, issues? }
}
| Layer | Codes | Rule |
|---|---|---|
source |
workflow_too_large, workflow_encoding_invalid |
at most 262,144 bytes, a leading byte order mark included, and well-formed UTF-8 |
yaml |
workflow_yaml_document_count, workflow_yaml_directive, workflow_yaml_duplicate_key, workflow_yaml_key_type, workflow_yaml_anchor, workflow_yaml_merge_key, workflow_yaml_tag, workflow_number_invalid, workflow_yaml_syntax |
YAML 1.2 with the core schema: one document as YAML 1.2 counts them, no directive, unique string keys, no anchor, alias, merge key, or explicit tag, and numbers that are finite and, when integral, within ±9,007,199,254,740,991; the character, byte order mark, escape, and nesting depth rules and any other parser error or warning are workflow_yaml_syntax |
version |
spec_incompatible |
a canonical spec that is newer or major-mismatch for the implemented version |
schema |
workflow_schema_invalid |
schemas/ci_actions.schema.json; issues carries the schema errors, sorted, at most 32 |
The yaml layer follows ci-actions, Parsing, on the syntax tree of the yaml package:
- A C0 control character other than tab, line feed, and carriage return fails anywhere, a quoted scalar included. DEL, C1 control characters other than NEL, U+FFFE, U+FFFF, and U+FEFF are content inside a single- or double-quoted scalar token and fail anywhere else.
- Runs of U+FEFF at the start of a line are byte order marks, not content, unless the line lies within a document's content: after the line on which the document begins and before the end of its last content token. The file's own leading byte order mark is the first of them. Any other U+FEFF outside a quoted scalar fails.
- In a double-quoted scalar, a
\uhigh surrogate escape directly followed by a\ulow surrogate escape is one character; every other surrogate escape, the\Uform included, fails. - Collections nest at most 64 deep, the root collection at depth 1; a deeper collection fails and is not read.
Within the yaml layer a rule of the subset is reported before a syntax violation, and among the rules the first in the text. Positions in messages count the characters of the text without its byte order marks. Integer tokens keep their exact value, float tokens are converted to binary64, negative zero becomes zero, and quoted scalars are always strings. The document is built from the YAML syntax tree in declaration order. JavaScript enumerates integer-like object keys first, so mappingKeyOrder carries the declaration order of every mapping by JSON Pointer; matrix expansion follows it. The function throws CiCompilerUsageError when its argument is not a Uint8Array.
Compiled Runner Jobs
checkCiRunnerJob decides whether a value is a coherent compiled runner job with its secret values inserted (runner protocol, Job Coherence), as the coordinator checks it before enqueueing and the runner before acceptance:
import { checkCiRunnerJob } from '@ship.zone/ci-compiler';
const result = checkCiRunnerJob(JSON.parse(body));
if (!result.ok) {
// result.failure: { code, message, location?, issues? }
}
It checks the rows of the Job Coherence table in order and reports the job failure code of the first row the job violates; the codes are the specification's runnerJobFailureCodes:
| Code | Rule |
|---|---|
spec_incompatible |
a canonical spec that is newer or major-mismatch for the implemented version |
job_schema_invalid |
schemas/runner-job.schema.json; issues carries the schema errors, sorted, at most 32 |
job_secret_incoherent |
in an oci-image job, a build secret names no secrets member, or a member is named by no build secret |
job_limit_incoherent |
source metadata, a declared artifact or cache maximum, or a declaration count above requirements.limits |
job_resources_incoherent |
resources.sharedMemoryBytes above resources.memoryBytes |
job_environment_incoherent |
a step without SHIPZONE_CI_MATRIX_JSON and SHIPZONE_CI_INPUTS_JSON as RFC 8785 JSON object text, another SHIPZONE_CI_* name, a secret target with that prefix, or step environment names, secret targets, and reserved names that repeat or number more than 128 |
job_command_limit_exceeded |
a step command argument above 16,384 UTF-8 bytes, which the schema bounds only in characters, or a step command above 131,072 UTF-8 bytes |
job_secret_inadmissible |
a secret value shorter than 8 UTF-8 bytes |
job_digest_mismatch |
compiledPlanDigest is not the digest of the plan projection |
A planned job, whose secrets values are true, fails the schema: values enter a job only for a lease. Matching a job against a runner's capability snapshot and checking lease coherence are coordinator and runner concerns outside the job and are not part of this check. No failure message contains a secret value.
Candidate Index Assembly
assembleCiCandidateIndex builds the multi-platform OCI image index of a build job from the accepted image records of its per-platform build jobs, in the declared platform order:
import { assembleCiCandidateIndex } from '@ship.zone/ci-compiler';
const candidate = assembleCiCandidateIndex(['linux/amd64', 'linux/arm64/v8'], imageRecords);
candidate.index; // { schemaVersion: 2, mediaType, manifests }
candidate.bytes; // the RFC 8785 bytes to push
candidate.digest; // 'sha256:…'
It throws CiCompilerUsageError unless every platform is a distinct linux platform with exactly one image record and every record belongs to a platform.
Conformance
The tests run the conformance data of the installed @ship.zone/ci-spec: every case of version-cases.json and digest-cases.json, every case of runner-job-cases.json with its exact job failure code, which covers every runner-jobs fixture, the job of every runner-messages.json lease, and every compiled job of compile-cases.json, whose plan digest is recomputed and which is checked with placeholder secret values. Every workflow of compile-cases.json and every ci-actions fixture is checked by checkCiWorkflow: a case of the source, yaml, version, or schema layer fails with its exact code, every other case passes with its workflow digest, and every valid fixture yields the values of the YAML in declaration order.
Verification
pnpm run build
pnpm run test:types
pnpm test
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.