@ship.zone/ci-spec 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.smartconfig.json +46 -0
  2. package/changelog.md +190 -0
  3. package/conformance/archive-cases.json +446 -0
  4. package/conformance/ci-actions/compile-invalid/build-arg-secret-collision.yml +31 -0
  5. package/conformance/ci-actions/compile-invalid/candidate-without-needs.yml +36 -0
  6. package/conformance/ci-actions/compile-invalid/needs-wider-triggers.yml +42 -0
  7. package/conformance/ci-actions/compile-invalid/npm-read-registry-not-allowlisted.yml +34 -0
  8. package/conformance/ci-actions/compile-invalid/publish-job-excludes-tag.yml +57 -0
  9. package/conformance/ci-actions/compile-invalid/shared-memory-above-memory.yml +28 -0
  10. package/conformance/ci-actions/compile-invalid/trigger-kind-without-job.yml +30 -0
  11. package/conformance/ci-actions/invalid/alias-affix-invalid.yml +33 -0
  12. package/conformance/ci-actions/invalid/alias.yml +32 -0
  13. package/conformance/ci-actions/invalid/bridge-network.yml +27 -0
  14. package/conformance/ci-actions/invalid/build-darwin-platform.yml +24 -0
  15. package/conformance/ci-actions/invalid/build-with-steps.yml +27 -0
  16. package/conformance/ci-actions/invalid/concurrency-empty-group.yml +28 -0
  17. package/conformance/ci-actions/invalid/concurrency-without-cancel-in-progress.yml +27 -0
  18. package/conformance/ci-actions/invalid/container-job-build-profile.yml +25 -0
  19. package/conformance/ci-actions/invalid/custom-tag.txt +23 -0
  20. package/conformance/ci-actions/invalid/duplicate-key.txt +24 -0
  21. package/conformance/ci-actions/invalid/egress-bare-wildcard.yml +29 -0
  22. package/conformance/ci-actions/invalid/egress-empty-allowlist.yml +28 -0
  23. package/conformance/ci-actions/invalid/egress-ip-literal.yml +29 -0
  24. package/conformance/ci-actions/invalid/egress-single-label-wildcard.yml +29 -0
  25. package/conformance/ci-actions/invalid/job-publish-permission.yml +28 -0
  26. package/conformance/ci-actions/invalid/job-trigger-undeclared-kind.yml +27 -0
  27. package/conformance/ci-actions/invalid/literal-alias-affix.yml +33 -0
  28. package/conformance/ci-actions/invalid/missing-spec.yml +24 -0
  29. package/conformance/ci-actions/invalid/multiple-documents.yml +25 -0
  30. package/conformance/ci-actions/invalid/npm-read-invalid-scope.yml +33 -0
  31. package/conformance/ci-actions/invalid/oci-darwin-platform.yml +25 -0
  32. package/conformance/ci-actions/invalid/permission-without-publication.yml +31 -0
  33. package/conformance/ci-actions/invalid/publish-order-duplicate.yml +50 -0
  34. package/conformance/ci-actions/invalid/publish-order-missing-kind.yml +48 -0
  35. package/conformance/ci-actions/invalid/publish-order-undeclared-kind.yml +45 -0
  36. package/conformance/ci-actions/invalid/publish-without-permission.yml +31 -0
  37. package/conformance/ci-actions/invalid/publish-without-tag-trigger.yml +31 -0
  38. package/conformance/ci-actions/invalid/release-notes-without-assets.yml +46 -0
  39. package/conformance/ci-actions/invalid/reserved-label-prefix.yml +25 -0
  40. package/conformance/ci-actions/invalid/reserved-label.yml +25 -0
  41. package/conformance/ci-actions/invalid/retention-above-maximum.yml +83 -0
  42. package/conformance/ci-actions/invalid/retention-below-minimum.yml +83 -0
  43. package/conformance/ci-actions/invalid/schema-version-field.yml +26 -0
  44. package/conformance/ci-actions/invalid/shared-memory-below-minimum.yml +28 -0
  45. package/conformance/ci-actions/invalid/shell-string.yml +21 -0
  46. package/conformance/ci-actions/invalid/spec-number.yml +25 -0
  47. package/conformance/ci-actions/invalid/spec-prerelease.yml +25 -0
  48. package/conformance/ci-actions/invalid/spec-range.yml +25 -0
  49. package/conformance/ci-actions/invalid/timeout-above-maximum.yml +26 -0
  50. package/conformance/ci-actions/invalid/timeout-below-minimum.yml +26 -0
  51. package/conformance/ci-actions/invalid/unknown-alias-kind.yml +31 -0
  52. package/conformance/ci-actions/invalid/unknown-key.yml +24 -0
  53. package/conformance/ci-actions/invalid/vm-darwin-candidate.yml +38 -0
  54. package/conformance/ci-actions/invalid/vm-darwin-shared-memory.yml +28 -0
  55. package/conformance/ci-actions/invalid/vm-oci-execution.yml +25 -0
  56. package/conformance/ci-actions/invalid/vm-unsupported-platform.yml +25 -0
  57. package/conformance/ci-actions/invalid/vm-without-platform.yml +24 -0
  58. package/conformance/ci-actions/valid/concurrency.yml +83 -0
  59. package/conformance/ci-actions/valid/egress.yml +40 -0
  60. package/conformance/ci-actions/valid/image-build.yml +80 -0
  61. package/conformance/ci-actions/valid/matrix.yml +45 -0
  62. package/conformance/ci-actions/valid/minimal.yml +25 -0
  63. package/conformance/ci-actions/valid/npm-read.yml +60 -0
  64. package/conformance/ci-actions/valid/release-assets.yml +114 -0
  65. package/conformance/ci-actions/valid/release-signing.yml +71 -0
  66. package/conformance/ci-actions/valid/release.yml +109 -0
  67. package/conformance/ci-actions/valid/resources.yml +79 -0
  68. package/conformance/ci-actions/valid/vm.yml +77 -0
  69. package/conformance/compile-cases.json +5885 -0
  70. package/conformance/compile-cases.schema.json +766 -0
  71. package/conformance/digest-cases.json +144 -0
  72. package/conformance/runner-cases.json +1238 -0
  73. package/conformance/runner-jobs/invalid/bridge-network.json +70 -0
  74. package/conformance/runner-jobs/invalid/build-darwin-platform.json +100 -0
  75. package/conformance/runner-jobs/invalid/build-with-steps.json +111 -0
  76. package/conformance/runner-jobs/invalid/build-without-microvm.json +100 -0
  77. package/conformance/runner-jobs/invalid/egress-empty-allowlist.json +71 -0
  78. package/conformance/runner-jobs/invalid/mutable-image.json +70 -0
  79. package/conformance/runner-jobs/invalid/npm-read-reserved-environment.json +82 -0
  80. package/conformance/runner-jobs/invalid/oci-darwin-platform.json +71 -0
  81. package/conformance/runner-jobs/invalid/push-permission-container.json +70 -0
  82. package/conformance/runner-jobs/invalid/shared-memory-below-minimum.json +71 -0
  83. package/conformance/runner-jobs/invalid/timeout-missing.json +69 -0
  84. package/conformance/runner-jobs/invalid/vm-without-microvm.json +82 -0
  85. package/conformance/runner-jobs/valid/candidate-test.json +72 -0
  86. package/conformance/runner-jobs/valid/egress.json +80 -0
  87. package/conformance/runner-jobs/valid/image-build.json +100 -0
  88. package/conformance/runner-jobs/valid/minimal.json +70 -0
  89. package/conformance/runner-jobs/valid/npm-read.json +81 -0
  90. package/conformance/runner-jobs/valid/resources.json +71 -0
  91. package/conformance/runner-jobs/valid/timeout-retention-defaults.json +91 -0
  92. package/conformance/runner-jobs/valid/vm-darwin.json +70 -0
  93. package/conformance/runner-jobs/valid/vm.json +82 -0
  94. package/conformance/runner-messages.json +2035 -0
  95. package/conformance/version-cases.json +125 -0
  96. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  97. package/dist_ts/00_commitinfo_data.js +9 -0
  98. package/dist_ts/constants.d.ts +199 -0
  99. package/dist_ts/constants.js +106 -0
  100. package/dist_ts/index.d.ts +1 -0
  101. package/dist_ts/index.js +2 -0
  102. package/dist_ts/plugins.d.ts +1 -0
  103. package/dist_ts/plugins.js +3 -0
  104. package/examples/ci_actions.yml +64 -0
  105. package/license.md +21 -0
  106. package/package.json +72 -0
  107. package/readme.md +146 -0
  108. package/schemas/ci_actions.schema.json +1664 -0
  109. package/schemas/runner-job.schema.json +1172 -0
  110. package/spec/ci-actions.md +433 -0
  111. package/spec/runner-protocol.md +378 -0
  112. package/spec/runner.openapi.json +2182 -0
  113. package/ts/00_commitinfo_data.ts +8 -0
  114. package/ts/constants.ts +112 -0
  115. package/ts/index.ts +1 -0
  116. package/ts/plugins.ts +1 -0
@@ -0,0 +1,433 @@
1
+ # ci_actions.yml Draft Standard
2
+
3
+ Status: draft. Versioned as the `@ship.zone/ci-spec` package; see Specification Version.
4
+
5
+ The workflow file is named `ci_actions.yml` and lives at repository root. It is source-controlled authoring input for a coordinator, not runner input.
6
+
7
+ Statements that no conformance case can observe are marked *(non-testable)*.
8
+
9
+ ## Parsing
10
+
11
+ The coordinator reads the workflow as bytes from the exact source commit being executed. A file of more than 262,144 bytes (256 KiB) fails with `workflow_too_large`, and bytes that are not well-formed UTF-8 fail with `workflow_encoding_invalid`. The file may begin with one byte order mark (U+FEFF), which is not part of the document; the size limit and the raw workflow SHA-256 count every byte as read, including it.
12
+
13
+ Parsing uses YAML 1.2 with the core schema and requires:
14
+
15
+ - exactly one document: an empty file, a file of only comments, and a file with a second document fail with `workflow_yaml_document_count`;
16
+ - no YAML or TAG directive (`workflow_yaml_directive`);
17
+ - unique keys in every mapping (`workflow_yaml_duplicate_key`);
18
+ - string mapping keys: every key is a scalar whose core-schema resolution is a string, so a plain key such as `1`, `true`, `null`, or `~`, and every collection used as a key, fails with `workflow_yaml_key_type`; such a key is written quoted, for example `"1"`;
19
+ - no anchor and no alias (`workflow_yaml_anchor`);
20
+ - no merge key: a `<<` key, plain or quoted, fails with `workflow_yaml_merge_key`;
21
+ - no explicit tag, including the standard `!!` tags and the non-specific tag `!` (`workflow_yaml_tag`).
22
+
23
+ Every other parser error or warning fails with `workflow_yaml_syntax`. The version check (see Specification Version) and JSON Schema validation against `schemas/ci_actions.schema.json`, which fails with `workflow_schema_invalid`, run only after these YAML-level rules pass.
24
+
25
+ Every plain scalar that the core schema resolves to an integer or a float is converted from its original token before schema validation; quoted scalars are strings and never numbers:
26
+
27
+ - an integer token, decimal, `0o` octal, or `0x` hexadecimal, keeps its exact value and is rejected when its magnitude exceeds 9,007,199,254,740,991;
28
+ - a float token is converted to IEEE 754 binary64 using round-to-nearest, ties-to-even, and is rejected when the result is not finite (`.inf`, `.nan`, and tokens that overflow, such as `1e400`) or when it is an integral value whose magnitude exceeds 9,007,199,254,740,991 (for example `9007199254740993.0` or `1e20`);
29
+ - negative zero becomes zero, which RFC 8785 serializes as `0`.
30
+
31
+ A rejected number fails with `workflow_number_invalid`.
32
+
33
+ ## Specification Version
34
+
35
+ A workflow carries exactly one version reference: the required top-level `spec`, the `@ship.zone/ci-spec` package version the file is written against, as an exact canonical `MAJOR.MINOR.PATCH` string, for example `spec: 2.0.0`. Ranges, prefixes, prerelease or build metadata, and YAML numbers such as `1.0` are schema errors. No other workflow field names a version.
36
+
37
+ A coordinator compiles the workflow only when its implemented version accepts `spec` under the rule in the runner protocol's Specification Version section; any other outcome fails compilation before a job is enqueued. The coordinator checks `spec` before JSON Schema validation: when the document is a mapping whose `spec` is a string in the canonical form, a `newer` or `major-mismatch` outcome fails with `spec_incompatible`, so a workflow written against another version is reported as such and not through the schema errors its constructs cause. A missing, non-string, or malformed `spec` fails schema validation. Every compiled job carries the workflow's `spec`. A workflow that uses a construct introduced in a later minor version declares that version or a later one.
38
+
39
+ ## Compilation Boundary
40
+
41
+ The coordinator binds compilation to:
42
+
43
+ - repository identity;
44
+ - exact source object ID;
45
+ - raw workflow SHA-256;
46
+ - `spec`;
47
+ - trust class (see Trust);
48
+ - resolved permissions;
49
+ - expanded job DAG, including per-platform build jobs and candidate assembly nodes;
50
+ - resolved network declarations;
51
+ - the `compiledPlanDigest` of every compiled job.
52
+
53
+ The compiled snapshot is immutable for the run. A runner receives only the compiled job schema and never parses this file. `compiledPlanDigest` is calculated from the projection defined by the runner protocol after all deterministic compilation and policy resolution, but before secret values are inserted. Compiled Jobs defines every member of a compiled job, so equal inputs compile to byte-identical jobs and digests in every conforming coordinator.
54
+
55
+ The specification defines no digest of a run as a whole. Only compiled jobs reach a runner, and each carries its own `compiledPlanDigest`; the other bound values are coordinator records that no runner or conformance case observes. A coordinator that needs one identity for its stored snapshot derives it in its own format.
56
+
57
+ Secrets remain references during parsing and compilation. They are resolved only after trust and permission evaluation, as defined in Secrets: untrusted runs, which include every pull request run (see Trust), receive no secret value at all. Untrusted runs cannot read trusted cache namespaces.
58
+
59
+ A compilation failure fails the run before any job is enqueued and before any secret value is resolved. Determining whether a stored value is admissible (see Secret Values) uses only its length and does not resolve it; a value is resolved only into a lease. Every compilation failure carries exactly one stable code, defined in Compilation Failures.
60
+
61
+ Candidate references compile to names, never digests. The coordinator resolves them to digests only when it leases a job, through lease bindings outside the compiled plan.
62
+
63
+ ## Triggers
64
+
65
+ This draft supports:
66
+
67
+ - branch push;
68
+ - tag push;
69
+ - pull request;
70
+ - manual dispatch with bounded typed inputs.
71
+
72
+ Ref names are UTF-8 strings: a forge that hosts repositories for a coordinator rejects, at push, a ref name that is not well-formed UTF-8, so every run has a UTF-8 ref name. Branch and tag filters match the complete short ref name without a `refs/heads/` or `refs/tags/` prefix. Matching is case-sensitive, anchored at both ends, and operates on Unicode scalar values. The grammar is:
73
+
74
+ - `*` matches zero or more Unicode scalar values except `/`;
75
+ - `**` matches zero or more Unicode scalar values including `/`;
76
+ - `?` matches exactly one Unicode scalar value except `/`;
77
+ - `\` escapes the following `*`, `?`, or `\` as a literal;
78
+ - every other scalar value is literal.
79
+
80
+ A run of three or more unescaped `*`, a trailing `\`, and an escape of any other scalar value are invalid; a workflow with an invalid pattern fails with `trigger_pattern_invalid` in every run. There are no character classes, braces, extglobs, or negated patterns. For example, `main` matches only `main`, `release/*` matches `release/1` but not `release/next/1`, and `release/**` matches both. Schedule triggers and deployment environments are outside this draft. `conformance/compile-cases.json` fixes the outcome of representative patterns in `patternCases`.
81
+
82
+ The trigger kinds are the keys of `on`: `push`, `tag`, `pullRequest`, and `manual`. A run has the kind of the trigger that started it. An event starts a run only when `on` declares its kind and the event matches the kind's filter:
83
+
84
+ - a branch push matches `push.branches`, or every branch without it;
85
+ - a tag push matches `tag.tags`, or every tag without it;
86
+ - a pull request matches `pullRequest.targetBranches` against its target branch, or every target branch without it;
87
+ - a manual dispatch always matches a declared `manual` trigger.
88
+
89
+ An event that does not match is *not triggered*: it creates no run. Triggers are evaluated only for a workflow that passes the source, YAML, version, schema, and workflow layers of Compilation Failures; for a commit whose workflow fails one of these layers, every event creates a run that fails with that layer's code.
90
+
91
+ A *protected tag run* is a run for which every condition holds:
92
+
93
+ - the run was triggered by a tag push, and the workflow was read from the tagged commit;
94
+ - the tag matches a repository protected-tag rule that authorizes the pushing actor; protected-tag rules are coordinator settings, never workflow content;
95
+ - the tagged commit is reachable from a protected branch;
96
+ - the run's trust class is trusted (see Trust).
97
+
98
+ Pull request, branch push, and manual runs are never protected tag runs. Only protected tag runs publish (see Publication) and receive protected secrets (see Secrets), and they have a cache namespace of their own (see Jobs and Steps). A workflow with a `publish` block must declare a `tag` trigger.
99
+
100
+ ## Trust
101
+
102
+ Every run has one *trust class*, trusted or untrusted. The coordinator derives it when it creates the run, from the run's kind and actor alone:
103
+
104
+ - A pull request run is untrusted, whatever its author, the permissions of whoever opened or updated the pull request, and the repository its head commit comes from.
105
+ - A branch push, tag push, or manual run is trusted when its actor is a repository writer, and untrusted otherwise. The actor of a push run is the actor that pushed the ref update the run executes; the actor of a manual run is the actor that dispatched it.
106
+
107
+ A *repository writer* is an actor that the repository's access control permits to push to the repository at the moment the coordinator creates the run. Workflow content and deployment policy never change a run's trust class. The coordinator fixes the trust class when it creates the run, so a later change of the actor's permissions changes neither the class nor anything derived from it.
108
+
109
+ The trust class decides which runs receive secret values (see Secrets) and npm read grants (see npm Read Access) and which runs can be protected tag runs (see Triggers). It is part of the cache namespace (see Jobs and Steps), the concurrency key (see Concurrency), and the candidate repository (see Candidates). The coordinator leases a job only to a runner whose registration allows the trust class of its run (see the runner protocol's Capabilities and Matching).
110
+
111
+ ## Concurrency
112
+
113
+ `concurrency` optionally limits which runs of a repository execute at the same time. It applies to runs of every trigger kind. It is not part of any compiled job, so it never changes a compiled job or a plan digest.
114
+
115
+ The *concurrency key* of a run whose workflow declares `concurrency` consists of:
116
+
117
+ - the tenant and repository;
118
+ - the run's trust class, and whether the run is a protected tag run, as for the cache namespace (see Jobs and Steps);
119
+ - the run's ref:
120
+ - a branch push run uses `refs/heads/<branch>`;
121
+ - a tag push run uses `refs/tags/<tag>`;
122
+ - a pull request run uses the pull request, which is the same for every run of that pull request whatever its head commit;
123
+ - a manual run uses the full ref name whose commit it executes;
124
+ - `group`, compared as an exact string without normalization.
125
+
126
+ `group` is a literal: it has no patterns, expressions, or interpolation. The coordinator fixes a run's key when it compiles the run. Two runs affect each other only when they have the same key. So a run whose workflow declares no `concurrency`, or whose compilation failed, never waits for, cancels, or is canceled by another run. An untrusted run never delays or cancels a trusted run. A run that is not a protected tag run never delays or cancels a protected tag run.
127
+
128
+ A run with a key is *pending* from its compilation until it *starts*, and it is *active* from its start until it is terminal. A run is terminal when every job of its compiled DAG is terminal and its qualification and publications, if any, have ended. The coordinator enqueues no job of a pending run. A run starts only when no other run with its key is active, and runs with one key start in the order the coordinator created them. At most one run per key is therefore active.
129
+
130
+ The `cancelInProgress` of the newly created run decides what happens to older runs with its key:
131
+
132
+ - `false`: the new run waits, and no run is canceled.
133
+ - `true`: the coordinator cancels every older pending run with the key. It also cancels the older active run with the key, unless that run has begun qualification (see Publication). A run that has begun qualification or publication is never canceled by concurrency; the new run waits until it is terminal.
134
+
135
+ Canceling a pending run makes it terminal without enqueuing any of its jobs. Canceling an active run works as follows:
136
+
137
+ - every job of the run that is not yet enqueued, and every queued job, becomes `canceled` without a lease;
138
+ - every leased or running attempt receives a cancellation directive with reason `superseded` (see the runner protocol's Cancellation);
139
+ - no job of the run is leased again;
140
+ - the run publishes nothing.
141
+
142
+ The new run starts once the canceled run is terminal. A publication resume re-executes no job and is not a run for concurrency.
143
+
144
+ ## Permissions
145
+
146
+ Permissions are closed coordinator-enforced declarations. Workflow permissions are:
147
+
148
+ - `source: read` grants the immutable source archive only;
149
+ - `artifacts: none|write` controls attempt-bound artifact upload grants;
150
+ - `caches: none|read|read-write` controls trust-scoped cache grants;
151
+ - `images: none|candidate|publish`: `candidate` permits build jobs, which push candidates; `publish` additionally permits image publications;
152
+ - `npm: none|publish` permits npm publications;
153
+ - `releaseAssets: none|publish` permits release-asset publications.
154
+
155
+ A publish permission is present exactly when the `publish` block declares entries of that kind; both directions are schema errors.
156
+
157
+ Runners do not decide repository authorization. Commit status publication is coordinator-owned. Job permissions contain `source`, `artifacts`, `caches`, and `images: none|candidate`; publish rights are coordinator-only and never belong to a job. Job permissions inherit the workflow permissions when omitted, with `images: publish` inherited as `candidate`. When present, the complete job permission object may only reduce them: `write` to `none` for artifacts, `read-write` to `read` or `none`, or `read` to `none`, for caches, and `candidate` to `none` for images. Permission escalation fails with `permission_escalation`. A build job requires resolved `images: candidate` and compiles to `images: push`; every other job compiles to `images: none`.
158
+
159
+ A declaration never exceeds the resolved job permissions: an artifact requires `artifacts: write`, a cache requires `caches: read` or `read-write`, and a cache with `access: read-write` requires `caches: read-write`. A declaration beyond them, and a build job without resolved `images: candidate`, fail with `permission_insufficient`; a declaration is never dropped.
160
+
161
+ ## Jobs and Steps
162
+
163
+ Jobs form an acyclic graph through `needs`. A dependency on a job the workflow does not declare fails with `needs_unknown`; a cycle, including a self-dependency, fails with `needs_cycle`; and expansion above the expanded-job ceiling (see Limits) fails with `expansion_limit_exceeded`.
164
+
165
+ `runner.profile` selects the job shape. An `oci` job is a container job with `execution`, `steps`, and optionally `matrix`, `environment`, `artifacts`, and `caches`. A `vm` job has the same shape with `execution.profile: vm`; see VM Jobs. An `oci-image` job is a build job with `build` and at most one cache; it has no steps, matrix, environment, or artifacts. `runner.platform` optionally pins an `oci` job to one native platform and is required for a `vm` job; build jobs declare platforms in `build.platforms` instead. `oci` and `oci-image` jobs execute only `linux` platforms, so the `runner.platform` of an `oci` job and every entry of `build.platforms` is a `linux` platform; `darwin/arm64` is defined only for `vm` jobs.
166
+
167
+ Steps use argument arrays. Shell strings, arbitrary expression languages, GitHub action compatibility, and unpinned third-party actions are not supported. The coordinator preserves declaration order and emits an ordered compiled `steps` array. Each compiled step contains its command and the complete merged non-secret environment; runners do not perform workflow-level environment merging.
168
+
169
+ Environment maps are merged in workflow, job, then step order: a name defined at more than one of these levels takes the value of the innermost one. The workflow environment applies only to the steps of `oci` and `vm` jobs. Secret references are job-wide and are injected into every step only after trust evaluation. A secret target equal to a name of the merged environment of any step of the job, two secret references with one target, and any `SHIPZONE_CI_*` name in an environment map or as a secret target fail with `environment_conflict`. A step whose merged environment names, secret targets, and reserved names (see the runner protocol's OCI Container Profile) number more than 128 fails with `environment_limit_exceeded`. A step whose command arguments total more than 131,072 UTF-8 bytes fails with `command_limit_exceeded`.
170
+
171
+ Artifact and cache names must be unique within a job; a repeated name fails with `transfer_name_duplicate`. Cache paths must not overlap each other: two paths overlap when, split at `/` with empty and `.` segments removed, the segments of one are a prefix of the segments of the other. Overlapping paths fail with `cache_path_overlap`. Artifacts and caches are `tar.gz` archives with explicit compressed-byte, extracted-byte, and entry ceilings. Produced digest and size metadata is measured at transfer time and is not part of the compiled declaration.
172
+
173
+ A cache `key` is a literal and compiles unchanged. The cache namespace of a run is tenant, repository, trust class, and whether the run is a protected tag run (see Triggers): protected tag runs have a namespace of their own, which they alone read and write, and they neither read nor write the namespace of any other trusted run. A cache a protected tag run restores was therefore written by a protected tag run of the same repository. Within one namespace, every job that declares the same key, including every expansion of one matrix job, reads and writes the same cache; the last successful publication wins. A workflow that needs separate caches, for example one per compilation target, declares separate keys in separate jobs.
174
+
175
+ `retentionDays` sets how long a published artifact or cache stays available, in whole days from 1 to 3,650. An artifact without it is retained for 30 days and a cache without it for 7 days. It compiles unchanged: the compiled declaration carries `retentionDays` exactly when the workflow declares it, and the runner does not use it. The coordinator enforces retention as defined in the runner protocol's Retention. A cache with `access: read` publishes nothing, so its `retentionDays` has no effect. Retention of job artifacts never affects release assets or npm packages that were published from them.
176
+
177
+ ### Job Triggers
178
+
179
+ `triggers` optionally restricts a job to runs of the listed trigger kinds, each of which the workflow declares in `on`. It lists kinds only: it has no patterns, expressions, or interpolation. The *admitted kinds* of a job are its `triggers`, or every kind declared in `on` when `triggers` is omitted.
180
+
181
+ A job is *skipped* in a run whose kind it does not admit. The coordinator decides this during compilation from the run's kind alone. A skipped job and all its expansions, which are its matrix expansions, per-platform build jobs, and candidate assembly node, are left out of the run's compiled DAG: they produce no compiled job and no plan digest, are never enqueued or leased, resolve no secret, and do not affect the run's result. `triggers` is not part of the compiled job, so declaring or removing it never changes the compiled job of a run that executes the job.
182
+
183
+ The following are compilation errors in every run, whatever its kind:
184
+
185
+ - a job admits a kind that a job in its `needs` does not admit (`needs_trigger_wider`). Every job's admitted kinds are therefore a subset of the admitted kinds of each job it needs, so a job that needs a skipped job is itself skipped, and no executed job ever waits for a skipped one;
186
+ - a job that `publish` references, as the build job of an image candidate or as the job of an artifact reference, does not admit `tag` (`publish_reference_invalid`);
187
+ - a kind declared in `on` is admitted by no job, so that no run consists only of skipped jobs (`trigger_kind_unused`).
188
+
189
+ Limits count every job of the workflow, whether or not a run skips it.
190
+
191
+ ## Resources
192
+
193
+ `resources` is optional on every job and applies to every job the declaration expands to. `memoryBytes`, `cpus`, `pids`, and `sharedMemoryBytes` compile unchanged into the compiled job `resources`; `workspaceBytes` and `workspaceEntries` compile into `requirements.limits.maximumWorkspaceBytes` and `requirements.limits.maximumWorkspaceEntries`. An omitted value takes the deployment default. `sharedMemoryBytes` greater than `memoryBytes` of the same job fails with `resources_invalid`.
194
+
195
+ The runner enforces each value as a ceiling for the whole attempt: `memoryBytes` for all its processes, `cpus` as CPU time in units of one CPU, `pids` as the number of concurrent processes and threads, and `sharedMemoryBytes` as the size of `/dev/shm` inside the sandbox. Resources are matched against runner capabilities as defined by the runner protocol.
196
+
197
+ ## Timeout
198
+
199
+ `timeoutMs` is the execution deadline, in milliseconds from 1,000 to 86,400,000, of every attempt of every job the declaration expands to. A job without `timeoutMs` compiles to 3,600,000 (one hour). Every compiled job carries its value explicitly, so the plan digest covers it. Each matrix expansion and each per-platform build job receives the full value for each of its attempts. A candidate assembly node has no timeout. The runner protocol's Job Timeout defines where the deadline starts, what it covers, and how it is enforced. A job that ends `timed_out` prevents the run's publications like every other unsuccessful job (see Publication).
200
+
201
+ ## VM Jobs
202
+
203
+ A `vm` job runs its steps with root privileges inside one microVM guest per attempt; the runner protocol's VM Profile defines the execution. It declares `runner.platform`, which is a `linux` platform or `darwin/arm64`, and compiles to `requirements.executionProfile: vm`, `requirements.isolation: microvm`, and `requirements.platform` equal to `runner.platform`. A `darwin/arm64` job uses a digest-pinned image, because a candidate is an OCI image and never a bootable `darwin` VM image, and declares no `sharedMemoryBytes`. Jobs that need a container engine, loop devices, or other kernel facilities use `vm`: the job image brings the engine, for example `dockerd`, and the step that uses it starts it.
204
+
205
+ ## Secrets
206
+
207
+ A job `secrets` entry references a repository secret by `name` and names its `environment` target. Secret values are coordinator-held repository settings, never workflow content. The compiled job carries the targets, which the plan digest covers, and the coordinator inserts the values only into leases.
208
+
209
+ An untrusted run receives no secret value, whether or not the secret is marked protected. Compiling an untrusted run in which a job that is not skipped references a secret fails with code `secret_untrusted`. Every pull request run is untrusted (see Trust), so a workflow with a `pullRequest` trigger restricts the jobs that reference secrets with `triggers`, so that pull request runs skip them instead of failing.
210
+
211
+ A repository may mark a secret *protected*; like protected-tag rules, the mark is a coordinator setting. Trusted runs receive unmarked secrets. A protected secret is delivered only to jobs of protected tag runs. Compiling a trusted run that is not a protected tag run, in which a job that is not skipped references a protected secret, fails with code `protected_secret_denied`. This covers trusted branch push and manual runs, and trusted tag runs whose tag no protected-tag rule authorizes for the pushing actor or whose tagged commit is not reachable from a protected branch. The two rules never both apply, because every protected tag run is trusted. Before every lease of a job that references a protected secret, the coordinator evaluates the protected-tag-run conditions again; when they no longer hold, the job is not leased and completes `system_error`. A publication resume re-executes no job and so delivers no secret. A workflow with other triggers references a protected secret only from jobs restricted with `triggers: [tag]`.
212
+
213
+ Compiling a run in which a job that is not skipped references a secret the repository does not hold fails with code `secret_unknown`.
214
+
215
+ The secret and grant rules are evaluated over every reference of the run's jobs that are not skipped, before any code is chosen, and the first applicable code in this order is the failure: `secret_untrusted`, `npm_read_untrusted`, `secret_unknown`, `protected_secret_denied`, `secret_too_short`. An untrusted run therefore fails before any secret name is looked up, and its failure does not depend on which secrets the repository holds, which of them are protected, or their lengths.
216
+
217
+ Every secret value, protected or not, is subject to the runner's log redaction and to its rejection of artifacts and caches that contain it; see the runner protocol's Secret Redaction.
218
+
219
+ ### Secret Values
220
+
221
+ A secret value is *admissible* when it is at least 8 bytes in UTF-8.
222
+
223
+ The coordinator stores only admissible secret values. It refuses to store any other value, as a new secret or as the replacement of an existing one, with code `secret_too_short`, and stores nothing: a refused replacement leaves the previous value in place. The rule applies to every value from which the coordinator resolves a job's secret reference. It concerns the value as a whole: a value of several lines is admissible whatever the length of its lines, and the runner protocol's Secret Redaction defines which of its lines are redacted.
224
+
225
+ A coordinator never delivers a value that is not admissible, for example one it stored before it implemented this rule: compiling a run in which a job that is not skipped references such a value fails with code `secret_too_short`, unless a code that precedes it applies (see Secrets).
226
+
227
+ ## npm Read Access
228
+
229
+ `npmRead` gives a job read access to private npm scopes. Each entry names a registry base URL and the scopes whose packages the job installs from it. The entry compiles unchanged into the compiled job, so the plan digest covers it. The credential is an attempt-scoped npm grant in the lease, defined by the runner protocol's npm Read Grants, and never appears in the workflow or the compiled job.
230
+
231
+ The following fail with `npm_read_invalid`:
232
+
233
+ - a registry listed twice, or a scope listed under two registries;
234
+ - a registry whose host and port, 443 unless the URL names one, no entry of the job's `egress` allowlist permits: npm read access adds no network access;
235
+ - in a job with `npmRead`, an environment or secret target named `NPM_CONFIG_USERCONFIG`, or a build secret with id `npmrc`. Both are reserved for grant delivery, and the reserved environment name counts toward the 128 names of every step.
236
+
237
+ Deployment policy may reject a registry for which it cannot issue grants; compilation then fails with `policy_npm_registry_denied`. npm read grants are issued only to trusted runs: compiling an untrusted run in which a job that is not skipped declares `npmRead` fails with code `npm_read_untrusted`. Every pull request run is untrusted, whatever its author (see Trust), so a workflow that installs private scopes restricts those jobs with `triggers` or declares no `pullRequest` trigger.
238
+
239
+ ## Image Builds
240
+
241
+ A build job compiles to one `oci-image` runner job per entry of `build.platforms`, in declared order, plus one coordinator assembly node named after the job. Each expanded build job counts toward the expanded-job ceiling. Build jobs have no matrix.
242
+
243
+ - `context` and `dockerfile` are paths relative to the repository root; `target` selects a Dockerfile stage. Every entry of `platforms` is a `linux` platform.
244
+ - `buildArgs` are non-secret values compiled into the plan and possibly persisted in image history. Names starting with `BUILDKIT_` are rejected.
245
+ - `secrets` map a BuildKit secret `id` to the `environment` target of a job secret reference. Every build secret names a declared job secret target, and every declared target is named by at least one build secret. A build-argument name equal to a job secret target or build secret id, and a build secret or declared target that breaks these rules, fail with `build_input_conflict`.
246
+ - `labels` are author image labels. `org.opencontainers.image.revision`, `org.opencontainers.image.source`, `org.opencontainers.image.version`, and every key starting with `zone.ship.ci.` are reserved for the coordinator, which compiles `revision` as the source object ID, `source` as the repository URL, and `version` as the tag version when the run has one (see Publication).
247
+ - `baseImages` bind Dockerfile image names to digest-pinned images or candidates. The Dockerfile refers to a base image by its exact `name`, which is lowercase. Every other image source in the Dockerfile must be pinned by SHA-256 digest.
248
+ - A build cache has no paths; the runner stores BuildKit cache state in it.
249
+
250
+ ## Candidates
251
+
252
+ A candidate is the multi-platform OCI image index the coordinator assembles after every per-platform build of one build job succeeded. `needs: [job]` on a build job waits for that assembly.
253
+
254
+ - An image reference is either a digest-pinned string or `{ candidate: <build job> }`. Container jobs use candidates as `execution.image`; build jobs use them as `baseImages` entries.
255
+ - A job that references a candidate lists the producing build job in `needs`. A reference to a job that is not a build job, or whose build job the referencing job does not list in `needs`, fails with `candidate_reference_invalid`.
256
+ - A base-image candidate whose build job does not list every platform of the consuming build, and a container or VM job with a candidate `execution.image` and a `runner.platform` that the build job does not list, fail with `candidate_platform_missing`. A container job without `runner.platform` is not checked: its runner selects the manifest of its native platform when it pulls the candidate (see the runner protocol's Image Grants and Bindings).
257
+ - Bindings resolve only to candidates assembled in the same run, so candidates never cross runs or trust classes.
258
+ - Assembly is deterministic. The index has `schemaVersion: 2`, media type `application/vnd.oci.image.index.v1+json`, and one descriptor per recorded manifest in declared platform order, each with media type, digest, size, and platform (`os`, `architecture`, and `variant` when declared). It has no annotations. Its bytes are the RFC 8785 serialization, so equal inputs produce an equal digest.
259
+ - The coordinator pushes the index by digest into a candidate repository owned by the run's trust class under the same registry owner as the repository's publication destinations, so promotion can mount blobs. Trusted candidates are retained per repository policy. Untrusted candidates live only in a separate untrusted namespace, are deleted within 7 days, and are never promotable.
260
+ - *(non-testable)* Provenance and SBOM attestations are disabled in this draft. A future revision may let the coordinator attach them as OCI referrers of the candidate index.
261
+
262
+ ## Network
263
+
264
+ A job without `network` compiles to `{ mode: none }`. `{ mode: egress, allow: [...] }` permits outbound TCP to the listed hosts only; `ports` defaults to `[443]` and is always explicit in the compiled job. There is no implicit allowlist and no unrestricted mode: an empty `allow` list is invalid, IP literals and single-label names are invalid, and duplicate hosts fail with `network_host_duplicate`. Repository templates that need network access declare `egress` with the hosts they need. Deployment policy may reject entries during compilation, which then fails with `policy_egress_denied`, but never drops or adds entries silently. Enforcement semantics are defined by the runner protocol.
265
+
266
+ ## Publication
267
+
268
+ The `publish` block declares coordinator publications. Jobs never publish and never receive publication credentials.
269
+
270
+ The *tag version* of a tag run is the tag short name with one optional leading lowercase `v` removed, when the result is a SemVer 2.0.0 version, including its prerelease and build components; otherwise the run has no tag version. On a tag run without a tag version, a workflow whose `publish` block uses a `version` alias or an npm publication fails with `tag_version_required`. `conformance/compile-cases.json` fixes representative tag versions in `tagVersionCases`.
271
+
272
+ Publish permissions resolve to `none` unless the run is a protected tag run (see Triggers); otherwise the run executes its jobs but publishes nothing.
273
+
274
+ Pull request, branch push, and manual runs never publish. A manual dispatch can only resume the publications of an existing tag run: the dispatching maintainer must be authorized by the protected-tag rule, and the resume reuses that run's compiled plan, candidates, artifacts, and qualification record, re-executing only incomplete publications.
275
+
276
+ Qualification starts after every job of the run succeeded; any failed, canceled, timed-out, or `system_error` job prevents all publications. Qualification verifies the referenced candidates and artifacts, checks npm versions, and records the current target of every destination alias as its predecessor, or its absence. A referenced artifact that has expired (see the runner protocol's Retention) fails qualification. A publication or publication resume that needs an expired artifact fails, so artifact retention bounds how long a run's publications can be resumed.
277
+
278
+ - `images` promote a candidate to destinations. The coordinator copies or mounts the index and its manifests and blobs by digest, then sets every alias only when it still equals its recorded predecessor. The `version` alias is the optional `prefix`, the tag version, and the optional `suffix`, concatenated in that order; `literal` aliases use their `value`. A valid OCI tag matches `[A-Za-z0-9_][A-Za-z0-9._-]{0,127}`. An expanded `version` alias that is not a valid OCI tag, for example because the tag version carries `+` build metadata, fails compilation of every tag run with `alias_invalid`, and two aliases of one destination that expand to the same tag fail it with `alias_duplicate`; the aliases are expanded, and these rules checked, on every tag run, whether or not it is a protected tag run. On a registry the coordinator operates, the alias write is an atomic compare-and-set. On external registries it is a single-writer probe, write, and probe, which cannot exclude a concurrent writer between probe and write *(non-testable)*.
279
+ - `npm` publishes a job artifact whose archive contains exactly one `.tgz` package tarball. Its `package.json` version must equal the tag version. The tarball is published verbatim with the declared access and dist-tag to every listed registry.
280
+ - `releaseAssets` attach the files of job artifacts to the release of the tag, as defined in Releases.
281
+
282
+ Artifact references name a job without a matrix and an artifact it declares with `required: true` and `when: success`, and an image publication names a build job. A reference that breaks this fails with `publish_reference_invalid`. Publications run by kind in the order of `publish.order`, and each kind in declaration order; the first failure or conflict stops the remaining publications. `publish.order` lists exactly the publication kinds the block declares, each once. Without it the order is `images`, `npm`, `releaseAssets`. Every publication is idempotent: a destination that already holds exactly the published content is success, and any other existing content or a changed alias predecessor is a conflict that requires a human decision.
283
+
284
+ Destination credentials are coordinator-held secrets selected by destination. They never appear in workflows, compiled jobs, leases, grants, logs, or artifacts.
285
+
286
+ ### Releases
287
+
288
+ Every artifact referenced by `releaseAssets` or `releaseNotes` is verified during qualification. A release-asset artifact contains at least one entry, and every entry is a regular file at the archive root; each file becomes one release asset named by its file name. Directories, nested paths, links, and one asset name produced by two files anywhere in the run's release-asset publications fail qualification. A `releaseNotes` artifact, which requires `releaseAssets`, contains exactly one regular file at the archive root of at most 1 MiB of UTF-8 text.
289
+
290
+ The release-asset publications of a run publish one release for the tag. The coordinator creates it when it is absent, as a published release (never a draft), named after the tag short name, with the `releaseNotes` file as its body or an empty body, and marked as a prerelease exactly when the tag version has a prerelease component; a run without a tag version never marks it as a prerelease. An existing release that is a draft, or that has a different name, body, or prerelease mark, is a conflict. An existing asset with the same name and SHA-256 is success, and any other existing asset of that name is a conflict. Publication then uploads each missing asset, and succeeds only after the coordinator has downloaded every asset of the publication from the release's download location and matched its byte size and SHA-256. Publications never delete releases or assets; retention is a repository setting outside the workflow.
291
+
292
+ ## Matrix
293
+
294
+ A matrix has at most eight dimensions and 32 values per dimension. Numeric values use the binary64 and safe-integer rules from Parsing. Total expanded jobs across the workflow may not exceed 256. A matrix job expands to one job per combination of one value from every dimension, in *expansion order*: combinations are ordered lexicographically by dimension in declaration order, the first-declared dimension varying slowest, and by value in declared order within a dimension. Declaration order is the order of the keys in the YAML mapping, whatever their text. For each expanded job, the coordinator serializes the selected dimension/value object with RFC 8785 and inserts it as `SHIPZONE_CI_MATRIX_JSON` in every compiled step; a job without a matrix receives `{}`. Matrix values do not interpolate commands, labels, image names, paths, or other workflow fields.
295
+
296
+ Manual dispatch values are validated against declared input names, types, required flags, and choice options before compilation: a `string` input takes a JSON string, a `boolean` input a JSON boolean, and a `choice` input a JSON string equal to one of its `options`. A value for an undeclared input, a value of another type, a choice outside its options, and a missing required input fail with `manual_input_invalid`. Supplied values retain their JSON string, boolean, or choice-string types, are serialized as one RFC 8785 object, and are inserted as `SHIPZONE_CI_INPUTS_JSON` in every compiled step. Omitted optional inputs are absent and non-manual runs receive `{}`. Manual inputs do not interpolate any other workflow field.
297
+
298
+ ## Expanded DAG
299
+
300
+ A run's compiled DAG consists of *nodes*. Every job that the run does not skip expands to nodes, each with an identity that is unique in the run:
301
+
302
+ - a job without `matrix` or `build` expands to one job node whose identity is the job name, for example `test`;
303
+ - a matrix job expands to one job node per combination, whose identity is the job name followed by the zero-based position of the combination in expansion order in brackets, for example `test[0]`;
304
+ - a build job expands to one per-platform job node per entry of `build.platforms`, whose identity is the job name followed by the platform in brackets, for example `image[linux/arm64]`, and to one candidate assembly node whose identity is the job name.
305
+
306
+ Job names are identifiers, which contain no `[` or `]`, so identities never collide. A node of a job that lists `J` in `needs` depends on every job node of `J` when `J` has no `build`, and on the assembly node of `J` when `J` is a build job. An assembly node depends on the per-platform job nodes of its job. The *plan order* of a run's nodes is by job name in Unicode code point order and, within one job, by expansion order or `build.platforms` order, with the assembly node after the per-platform job nodes of its job. Coordinators name the jobs of a run by these identities, for example in commit statuses and run views.
307
+
308
+ ## Compiled Jobs
309
+
310
+ Every job node compiles to one compiled job, a function of the workflow, the run, and the deployment policy, so that equal inputs compile to byte-identical jobs and plan digests. A compiled job has exactly the following members; array order is as stated, and object member order is irrelevant because the plan digest is computed over RFC 8785.
311
+
312
+ - `spec`: the workflow's `spec`. `sourceObjectId`: the executed commit. `workflowDigest`: the lowercase SHA-256 of the raw workflow bytes (see Parsing).
313
+ - `requirements`:
314
+ - `executionProfile`: `runner.profile`;
315
+ - `isolation`: `microvm` for `vm` and `oci-image`; for `oci`, `container`, or `microvm` when deployment policy requires it (see the runner protocol's Isolation);
316
+ - `platform`: for `oci` and `vm`, `runner.platform` when declared; for a per-platform build job, its platform; absent otherwise;
317
+ - `labels`: `runner.labels` in declared order;
318
+ - `features`: `source.tar-gz`, followed by `artifacts` when the job declares an artifact, followed by `caches` when it declares a cache;
319
+ - `limits`: `maximumSourceArchiveBytes`, `maximumSourceExtractedBytes`, `maximumSourceEntries`, `maximumLogChunkBytes`, and `maximumTotalLogBytes` are the deployment values. `maximumArtifactArchiveBytes`, `maximumArtifactExtractedBytes`, and `maximumArtifactEntries` are the largest `maximumBytes`, `maximumExtractedBytes`, and `maximumEntries` of the job's artifacts, each taken separately, or 0 without artifacts, and `maximumArtifacts` is their number. The four cache limits are derived from the job's caches in the same way. `maximumWorkspaceBytes` and `maximumWorkspaceEntries` are `resources.workspaceBytes` and `resources.workspaceEntries`, or the deployment defaults when not declared.
320
+ - `permissions`: `source: read`, the resolved `artifacts` and `caches` (see Permissions), and `images: push` for a build job or `images: none` otherwise.
321
+ - `network`: `{ "mode": "none" }` without `network`; otherwise `{ "mode": "egress", "allow": [...] }` with the declared entries in declared order, each with its `host` and its `ports` as declared or `[443]`.
322
+ - `execution` (`oci`, `vm`): the declared `execution`, unchanged.
323
+ - `steps` (`oci`, `vm`): one compiled step per declared step, in declared order, with `name` when declared, `command` equal to `run`, and `environment` equal to the merged environment (see Jobs and Steps) together with `SHIPZONE_CI_MATRIX_JSON` and `SHIPZONE_CI_INPUTS_JSON`.
324
+ - `build` (`oci-image`): `context`, `dockerfile`, and `target` when declared; `buildArgs` as declared, or `{}`; `secrets` in declared order, or `[]`; `labels`, the declared labels together with `org.opencontainers.image.revision`, `org.opencontainers.image.source`, and, on a run with a tag version, `org.opencontainers.image.version` (see Image Builds); `baseImages` in declared order, or `[]`.
325
+ - `source`: the descriptor of the run's source archive.
326
+ - `secrets`: one member per secret reference of the job, named by its `environment` target; the value is inserted only into a lease.
327
+ - `timeoutMs`: the declared value, or 3,600,000.
328
+ - `resources`: the declared `memoryBytes`, `cpus`, `pids`, and `sharedMemoryBytes`; absent when the job declares none of them.
329
+ - `npmRead`: the declared entries, unchanged, when declared.
330
+ - `artifacts`: the declared artifacts in declared order, each with its declared members and `format: tar.gz`; `[]` without artifacts.
331
+ - `caches`: the declared caches in declared order, unchanged; `[]` without caches.
332
+
333
+ `conformance/compile-cases.json` fixes the compiled jobs, identities, and plan digests of representative runs.
334
+
335
+ ## Limits
336
+
337
+ - Workflow source: 256 KiB.
338
+ - Jobs before expansion: 128.
339
+ - Expanded jobs: 256.
340
+ - Steps per job: 128.
341
+ - Dependencies per job: 32.
342
+ - Matrix dimensions: 8.
343
+ - Values per matrix dimension: 32.
344
+ - Runner labels: 32.
345
+ - Build platforms per build job: 8.
346
+ - Build arguments: 64; build secrets: 32; author labels: 60; base images: 16 per build job.
347
+ - Egress allowlist: 64 entries, 16 ports per entry.
348
+ - npm read access: 8 registries per job, 16 scopes per registry.
349
+ - Publications: 16 image candidates with 8 destinations and 16 aliases each; 8 npm packages with 8 registries each; 64 release assets.
350
+ - Environment variables: 128 per scope.
351
+ - Secret references: 128 per job.
352
+ - Artifacts: 64 per job.
353
+ - Caches: 32 per job.
354
+ - Command: 128 arguments, 16 KiB each, 128 KiB total.
355
+ - Artifact archive: 16 GiB compressed, 128 GiB extracted, 1,000,000 entries per item.
356
+ - Cache archive: 16 GiB compressed, 128 GiB extracted, 1,000,000 entries per item.
357
+ - Workspace: 1 TiB and 2,000,000 entries.
358
+ - Job timeout: 1 second to 24 hours; 1 hour when omitted.
359
+ - Retention: 1 to 3,650 days; 30 days for an artifact and 7 days for a cache when omitted.
360
+
361
+ All objects reject unknown properties. Implementations may use lower deployment policy limits but must fail before enqueueing rather than silently truncate; a value above such a limit, including a source archive above the deployment source limits, fails with `policy_limit_exceeded`. A deployment maximum for the job timeout or for retention applies to the declared value, or to the default when none is declared: a value above it fails with `policy_timeout_exceeded` or `policy_retention_exceeded` and is never shortened. Any other refusal of deployment policy fails with `policy_denied`.
362
+
363
+ ## Compilation Failures
364
+
365
+ A compilation failure fails the run before any job is enqueued and carries exactly one code. The codes are grouped into layers, which the coordinator evaluates in the order of the table; the first layer with a violation decides the failure. Within the `source`, `yaml`, `version`, and `schema` layers the failure carries the code of any violation of that layer. Within the `workflow`, `policy`, and `run` layers it carries the first code, in table order, whose rule the workflow, run, and policy violate. Trigger matching (see Triggers) happens between the `workflow` and `policy` layers, so a run that is not triggered is never evaluated against the later layers. The `source` through `workflow` layers depend only on the workflow bytes, so every run of one commit fails them alike.
366
+
367
+ | Layer | Code | Rule |
368
+ | --- | --- | --- |
369
+ | source | `workflow_too_large` | the file has more than 262,144 bytes (Parsing) |
370
+ | source | `workflow_encoding_invalid` | the file is not well-formed UTF-8 (Parsing) |
371
+ | yaml | `workflow_yaml_syntax` | any other YAML parser error or warning (Parsing) |
372
+ | yaml | `workflow_yaml_document_count` | no document, or more than one (Parsing) |
373
+ | yaml | `workflow_yaml_directive` | a YAML or TAG directive (Parsing) |
374
+ | yaml | `workflow_yaml_duplicate_key` | a mapping repeats a key (Parsing) |
375
+ | yaml | `workflow_yaml_key_type` | a key that does not resolve to a string (Parsing) |
376
+ | yaml | `workflow_yaml_anchor` | an anchor or alias (Parsing) |
377
+ | yaml | `workflow_yaml_merge_key` | a `<<` key (Parsing) |
378
+ | yaml | `workflow_yaml_tag` | an explicit tag (Parsing) |
379
+ | yaml | `workflow_number_invalid` | a non-finite or unsafe number (Parsing) |
380
+ | version | `spec_incompatible` | a canonical `spec` that is `newer` or `major-mismatch` (Specification Version) |
381
+ | schema | `workflow_schema_invalid` | the document fails `schemas/ci_actions.schema.json` |
382
+ | workflow | `trigger_pattern_invalid` | an invalid branch or tag pattern (Triggers) |
383
+ | workflow | `needs_unknown` | `needs` names an undeclared job (Jobs and Steps) |
384
+ | workflow | `needs_cycle` | `needs` forms a cycle or self-dependency (Jobs and Steps) |
385
+ | workflow | `expansion_limit_exceeded` | more than 256 expanded jobs (Matrix, Image Builds) |
386
+ | workflow | `needs_trigger_wider` | a job admits a kind a job it needs does not (Job Triggers) |
387
+ | workflow | `trigger_kind_unused` | a kind in `on` that no job admits (Job Triggers) |
388
+ | workflow | `permission_escalation` | a job permission above the workflow permission (Permissions) |
389
+ | workflow | `permission_insufficient` | a declaration or build job beyond its resolved permissions (Permissions) |
390
+ | workflow | `environment_conflict` | a secret target or reserved-name conflict (Jobs and Steps) |
391
+ | workflow | `environment_limit_exceeded` | more than 128 names in a step (Jobs and Steps) |
392
+ | workflow | `command_limit_exceeded` | a command above 131,072 bytes (Jobs and Steps) |
393
+ | workflow | `transfer_name_duplicate` | a repeated artifact or cache name (Jobs and Steps) |
394
+ | workflow | `cache_path_overlap` | overlapping cache paths (Jobs and Steps) |
395
+ | workflow | `resources_invalid` | `sharedMemoryBytes` above `memoryBytes` (Resources) |
396
+ | workflow | `network_host_duplicate` | a repeated egress host (Network) |
397
+ | workflow | `npm_read_invalid` | a broken `npmRead` rule (npm Read Access) |
398
+ | workflow | `build_input_conflict` | a broken build argument or build secret rule (Image Builds) |
399
+ | workflow | `candidate_reference_invalid` | a candidate of a job that is not a needed build job (Candidates) |
400
+ | workflow | `candidate_platform_missing` | a candidate without a required platform (Candidates) |
401
+ | workflow | `publish_reference_invalid` | a broken artifact or candidate reference in `publish` (Job Triggers, Publication) |
402
+ | policy | `policy_limit_exceeded` | a value above a deployment limit (Limits) |
403
+ | policy | `policy_timeout_exceeded` | a timeout above the deployment maximum (Limits) |
404
+ | policy | `policy_retention_exceeded` | a retention above the deployment maximum (Limits) |
405
+ | policy | `policy_egress_denied` | an egress entry the deployment rejects (Network) |
406
+ | policy | `policy_npm_registry_denied` | an npm registry the deployment rejects (npm Read Access) |
407
+ | policy | `policy_denied` | any other refusal of deployment policy (Limits) |
408
+ | run | `manual_input_invalid` | a dispatch value that breaks its input declaration (Matrix) |
409
+ | run | `tag_version_required` | a tag run without a tag version needs one (Publication) |
410
+ | run | `alias_invalid` | an expanded alias that is not a valid OCI tag (Publication) |
411
+ | run | `alias_duplicate` | two aliases of one destination expand to one tag (Publication) |
412
+ | run | `secret_untrusted` | an untrusted run references a secret (Secrets) |
413
+ | run | `npm_read_untrusted` | an untrusted run declares `npmRead` (npm Read Access) |
414
+ | run | `secret_unknown` | a reference to a secret the repository does not hold (Secrets) |
415
+ | run | `protected_secret_denied` | a run that is not a protected tag run references a protected secret (Secrets) |
416
+ | run | `secret_too_short` | a referenced value is not admissible (Secret Values) |
417
+
418
+ Rules of the `policy` and `run` layers apply only to jobs the run does not skip, and to the `publish` block only on tag runs. `conformance/compile-cases.json` lists the codes in table order as `failureCodes` and fixes the code of every invalid workflow fixture and of representative runs.
419
+
420
+ ## Security Defaults
421
+
422
+ - OCI images are pinned by SHA-256 or are same-run candidates bound by digest at lease time.
423
+ - Network is `none` unless a job declares an `egress` allowlist permitted by policy; there is no unrestricted network mode.
424
+ - Image builds run in a fresh microVM per attempt on dedicated builder runners; `vm` jobs get root privileges only inside their own per-attempt microVM guest.
425
+ - Registry grants are attempt-scoped and outside the plan digest; publication credentials stay with the coordinator.
426
+ - Source archives are immutable and digest-verified.
427
+ - Cache namespaces include tenant, repository, and trust class, and protected tag runs have a cache namespace of their own.
428
+ - Concurrency keys include trust class and whether the run is a protected tag run, so an untrusted run never cancels a trusted run and only protected tag runs cancel protected tag runs.
429
+ - Every pull request run is untrusted; branch push, tag push, and manual runs are trusted only when a repository writer started them.
430
+ - Secret values never appear in workflow snapshots, logs, or runner API errors; runners redact them from job logs and reject artifacts and caches that contain them. The coordinator stores no secret value shorter than 8 bytes (see Secret Values), so runners redact every stored value as a whole; a line shorter than 8 bytes of a value of several lines is not redacted.
431
+ - Untrusted runs receive no secret value, and protected secrets reach only jobs of protected tag runs.
432
+ - npm read grants are attempt-scoped, read-only, and issued only to trusted runs.
433
+ - Workflow changes in a pull request are treated as untrusted input.