@ship.zone/ci-spec 2.1.1 → 2.1.2

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.
package/changelog.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-09-27 - 2.1.2
4
+
5
+ ### Fixes
6
+
7
+ - settle the compilation readings the text left open (ci-actions, runner-protocol)
8
+ - Artifact names are unique among a job's artifacts and cache names among its caches: a transfer is identified by its kind and name, so an artifact and a cache of one job may share a name without failing `transfer_name_duplicate`.
9
+ - The expanded-job ceiling of 256 counts the job nodes of the expanded DAG: every per-platform build job counts, and a candidate assembly node, which is a coordinator node and not a job, does not.
10
+ - The 128 names of a step are the union of its merged environment, its job's secret targets, `SHIPZONE_CI_MATRIX_JSON`, `SHIPZONE_CI_INPUTS_JSON`, and, with `npmRead`, `NPM_CONFIG_USERCONFIG`, each counted once; a declared `NPM_CONFIG_USERCONFIG` in a job with `npmRead` therefore fails with `npm_read_invalid`, not `environment_limit_exceeded`.
11
+ - An `npmRead` registry listed twice is compared as an exact string, without normalizing a trailing `/` or an explicit port 443, as each entry receives a grant that carries its registry as written.
12
+ - An egress entry `*.example.com` matches every name below `example.com` at any depth, and not `example.com` itself; npm registry hosts are checked against the allowlist the same way.
13
+ - `NPM_CONFIG_USERCONFIG` and `npmrc` are reserved in a job with `npmRead` of every profile: a build job with the secret target `NPM_CONFIG_USERCONFIG` fails with `npm_read_invalid`, and so does an `oci` or `vm` job whose steps receive the name from the workflow environment, which does not apply to build jobs.
14
+ - `resources_invalid` compares only a declared `sharedMemoryBytes` with a declared `memoryBytes`; a shared memory size above the deployment's default memory is not a `workflow` layer failure.
15
+ - A compilation failure is normative only in its code; a location or message reported with it is informative.
16
+ - Lines end as in YAML 1.2, in a line feed, a carriage return and a line feed, or a carriage return alone, and line breaks in scalar content read as line feeds, so a file with carriage return line breaks holds the same document as the file with line feeds.
17
+ - A line that begins with U+FEFF ends a block scalar: after a keep-chomped `|+` or `>+` scalar, the empty lines before it are content of the scalar, and the line and the lines after it are not.
18
+ - Adds 14 compile vectors: 6 workflow-layer failures, 6 workflows that pass the workflow layer and fail the deployment's timeout maximum, and 2 compiled workflows with carriage return line breaks and a byte order mark line after a keep-chomped block scalar. The test's reference YAML layer reads carriage return line breaks and ends a block scalar at a byte order mark line.
19
+
3
20
  ## 2026-09-27 - 2.1.1
4
21
 
5
22
  ### Fixes
@@ -1681,6 +1681,348 @@
1681
1681
  "code": "publish_reference_invalid"
1682
1682
  }
1683
1683
  },
1684
+ {
1685
+ "id": "workflow-transfer-names-per-kind",
1686
+ "description": "An artifact and a cache of one job are both named results: artifacts and caches are separate name spaces. The workflow passes the workflow layer; the job's default timeout of 3,600,000 then exceeds the deployment maximum.",
1687
+ "workflow": {
1688
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: write\n caches: read-write\n images: none\n npm: none\n releaseAssets: none\njobs:\n test:\n artifacts:\n - name: results\n path: results\n when: always\n required: false\n maximumBytes: 1048576\n maximumExtractedBytes: 4194304\n maximumEntries: 100\n caches:\n - name: results\n key: results\n access: read-write\n paths:\n - .cache\n maximumBytes: 1048576\n maximumExtractedBytes: 4194304\n maximumEntries: 100\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1689
+ },
1690
+ "context": {
1691
+ "repository": {
1692
+ "tenantId": "tenant-1",
1693
+ "repositoryId": "example/app",
1694
+ "url": "https://code.foss.global/example/app"
1695
+ },
1696
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1697
+ "source": {
1698
+ "format": "tar.gz",
1699
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
1700
+ "sizeBytes": 1024,
1701
+ "extractedSizeBytes": 4096,
1702
+ "entryCount": 4
1703
+ },
1704
+ "trigger": {
1705
+ "kind": "push",
1706
+ "ref": "refs/heads/main"
1707
+ },
1708
+ "trustClass": "trusted",
1709
+ "protectedTagRun": false,
1710
+ "secrets": []
1711
+ },
1712
+ "policy": {
1713
+ "limits": {
1714
+ "maximumSourceArchiveBytes": 1048576,
1715
+ "maximumSourceExtractedBytes": 4194304,
1716
+ "maximumSourceEntries": 1000,
1717
+ "maximumLogChunkBytes": 65536,
1718
+ "maximumTotalLogBytes": 10485760,
1719
+ "maximumWorkspaceBytes": 268435456,
1720
+ "maximumWorkspaceEntries": 100000
1721
+ },
1722
+ "ociIsolation": "container",
1723
+ "maximumTimeoutMs": 1800000
1724
+ },
1725
+ "expected": {
1726
+ "outcome": "failed",
1727
+ "code": "policy_timeout_exceeded"
1728
+ }
1729
+ },
1730
+ {
1731
+ "id": "workflow-expansion-limit-assembly-node",
1732
+ "description": "31 by 8 matrix combinations and 8 build platforms are 256 expanded jobs; the assembly node of the build job is a 257th node and does not count. The workflow passes the workflow layer; the job's default timeout of 3,600,000 then exceeds the deployment maximum.",
1733
+ "workflow": {
1734
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: candidate\n npm: none\n releaseAssets: none\njobs:\n image:\n runner:\n profile: oci-image\n labels:\n - builder\n build:\n context: .\n dockerfile: Dockerfile\n platforms:\n - linux/amd64\n - linux/arm64\n - linux/arm/v7\n - linux/arm/v6\n - linux/386\n - linux/ppc64le\n - linux/s390x\n - linux/riscv64\n test:\n matrix:\n a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n b: [0, 1, 2, 3, 4, 5, 6, 7]\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1735
+ },
1736
+ "context": {
1737
+ "repository": {
1738
+ "tenantId": "tenant-1",
1739
+ "repositoryId": "example/app",
1740
+ "url": "https://code.foss.global/example/app"
1741
+ },
1742
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1743
+ "source": {
1744
+ "format": "tar.gz",
1745
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
1746
+ "sizeBytes": 1024,
1747
+ "extractedSizeBytes": 4096,
1748
+ "entryCount": 4
1749
+ },
1750
+ "trigger": {
1751
+ "kind": "push",
1752
+ "ref": "refs/heads/main"
1753
+ },
1754
+ "trustClass": "trusted",
1755
+ "protectedTagRun": false,
1756
+ "secrets": []
1757
+ },
1758
+ "policy": {
1759
+ "limits": {
1760
+ "maximumSourceArchiveBytes": 1048576,
1761
+ "maximumSourceExtractedBytes": 4194304,
1762
+ "maximumSourceEntries": 1000,
1763
+ "maximumLogChunkBytes": 65536,
1764
+ "maximumTotalLogBytes": 10485760,
1765
+ "maximumWorkspaceBytes": 268435456,
1766
+ "maximumWorkspaceEntries": 100000
1767
+ },
1768
+ "ociIsolation": "container",
1769
+ "maximumTimeoutMs": 1800000
1770
+ },
1771
+ "expected": {
1772
+ "outcome": "failed",
1773
+ "code": "policy_timeout_exceeded"
1774
+ }
1775
+ },
1776
+ {
1777
+ "id": "workflow-expansion-limit-build-platforms",
1778
+ "description": "31 by 8 matrix combinations, 8 build platforms, and 1 job are 257 expanded jobs: per-platform build jobs count.",
1779
+ "workflow": {
1780
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: candidate\n npm: none\n releaseAssets: none\njobs:\n image:\n runner:\n profile: oci-image\n labels:\n - builder\n build:\n context: .\n dockerfile: Dockerfile\n platforms:\n - linux/amd64\n - linux/arm64\n - linux/arm/v7\n - linux/arm/v6\n - linux/386\n - linux/ppc64le\n - linux/s390x\n - linux/riscv64\n lint:\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n test:\n matrix:\n a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n b: [0, 1, 2, 3, 4, 5, 6, 7]\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1781
+ },
1782
+ "expected": {
1783
+ "outcome": "failed",
1784
+ "code": "expansion_limit_exceeded"
1785
+ }
1786
+ },
1787
+ {
1788
+ "id": "workflow-environment-limit-names-counted-once",
1789
+ "description": "100 workflow names and 25 step names, NPM_CONFIG_USERCONFIG declared in the step, and the 2 reserved step names are 128 names in a job with npmRead: the declared reserved name counts once, so the limit holds and the step fails npm_read_invalid.",
1790
+ "workflow": {
1791
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\nenvironment:\n W000: w\n W001: w\n W002: w\n W003: w\n W004: w\n W005: w\n W006: w\n W007: w\n W008: w\n W009: w\n W010: w\n W011: w\n W012: w\n W013: w\n W014: w\n W015: w\n W016: w\n W017: w\n W018: w\n W019: w\n W020: w\n W021: w\n W022: w\n W023: w\n W024: w\n W025: w\n W026: w\n W027: w\n W028: w\n W029: w\n W030: w\n W031: w\n W032: w\n W033: w\n W034: w\n W035: w\n W036: w\n W037: w\n W038: w\n W039: w\n W040: w\n W041: w\n W042: w\n W043: w\n W044: w\n W045: w\n W046: w\n W047: w\n W048: w\n W049: w\n W050: w\n W051: w\n W052: w\n W053: w\n W054: w\n W055: w\n W056: w\n W057: w\n W058: w\n W059: w\n W060: w\n W061: w\n W062: w\n W063: w\n W064: w\n W065: w\n W066: w\n W067: w\n W068: w\n W069: w\n W070: w\n W071: w\n W072: w\n W073: w\n W074: w\n W075: w\n W076: w\n W077: w\n W078: w\n W079: w\n W080: w\n W081: w\n W082: w\n W083: w\n W084: w\n W085: w\n W086: w\n W087: w\n W088: w\n W089: w\n W090: w\n W091: w\n W092: w\n W093: w\n W094: w\n W095: w\n W096: w\n W097: w\n W098: w\n W099: w\njobs:\n test:\n network:\n mode: egress\n allow:\n - host: 'npm.example.com'\n npmRead:\n - registry: https://npm.example.com\n scopes:\n - '@example'\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n environment:\n S00: s\n S01: s\n S02: s\n S03: s\n S04: s\n S05: s\n S06: s\n S07: s\n S08: s\n S09: s\n S10: s\n S11: s\n S12: s\n S13: s\n S14: s\n S15: s\n S16: s\n S17: s\n S18: s\n S19: s\n S20: s\n S21: s\n S22: s\n S23: s\n S24: s\n NPM_CONFIG_USERCONFIG: /tmp/npmrc\n"
1792
+ },
1793
+ "expected": {
1794
+ "outcome": "failed",
1795
+ "code": "npm_read_invalid"
1796
+ }
1797
+ },
1798
+ {
1799
+ "id": "workflow-environment-limit-npm-read",
1800
+ "description": "100 workflow names, 24 step names, 2 secret targets, the 2 reserved step names, and NPM_CONFIG_USERCONFIG of a job with npmRead are 129 names.",
1801
+ "workflow": {
1802
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\nenvironment:\n W000: w\n W001: w\n W002: w\n W003: w\n W004: w\n W005: w\n W006: w\n W007: w\n W008: w\n W009: w\n W010: w\n W011: w\n W012: w\n W013: w\n W014: w\n W015: w\n W016: w\n W017: w\n W018: w\n W019: w\n W020: w\n W021: w\n W022: w\n W023: w\n W024: w\n W025: w\n W026: w\n W027: w\n W028: w\n W029: w\n W030: w\n W031: w\n W032: w\n W033: w\n W034: w\n W035: w\n W036: w\n W037: w\n W038: w\n W039: w\n W040: w\n W041: w\n W042: w\n W043: w\n W044: w\n W045: w\n W046: w\n W047: w\n W048: w\n W049: w\n W050: w\n W051: w\n W052: w\n W053: w\n W054: w\n W055: w\n W056: w\n W057: w\n W058: w\n W059: w\n W060: w\n W061: w\n W062: w\n W063: w\n W064: w\n W065: w\n W066: w\n W067: w\n W068: w\n W069: w\n W070: w\n W071: w\n W072: w\n W073: w\n W074: w\n W075: w\n W076: w\n W077: w\n W078: w\n W079: w\n W080: w\n W081: w\n W082: w\n W083: w\n W084: w\n W085: w\n W086: w\n W087: w\n W088: w\n W089: w\n W090: w\n W091: w\n W092: w\n W093: w\n W094: w\n W095: w\n W096: w\n W097: w\n W098: w\n W099: w\njobs:\n test:\n network:\n mode: egress\n allow:\n - host: 'npm.example.com'\n npmRead:\n - registry: https://npm.example.com\n scopes:\n - '@example'\n secrets:\n - name: token-one\n environment: TOKEN_ONE\n - name: token-two\n environment: TOKEN_TWO\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n environment:\n S00: s\n S01: s\n S02: s\n S03: s\n S04: s\n S05: s\n S06: s\n S07: s\n S08: s\n S09: s\n S10: s\n S11: s\n S12: s\n S13: s\n S14: s\n S15: s\n S16: s\n S17: s\n S18: s\n S19: s\n S20: s\n S21: s\n S22: s\n S23: s\n"
1803
+ },
1804
+ "expected": {
1805
+ "outcome": "failed",
1806
+ "code": "environment_limit_exceeded"
1807
+ }
1808
+ },
1809
+ {
1810
+ "id": "workflow-npm-read-registry-exact",
1811
+ "description": "https://npm.example.com and https://npm.example.com/ are two registries: registries are compared as exact strings. The workflow passes the workflow layer; the job's default timeout of 3,600,000 then exceeds the deployment maximum.",
1812
+ "workflow": {
1813
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\njobs:\n test:\n network:\n mode: egress\n allow:\n - host: 'npm.example.com'\n npmRead:\n - registry: https://npm.example.com\n scopes:\n - '@one'\n - registry: https://npm.example.com/\n scopes:\n - '@two'\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1814
+ },
1815
+ "context": {
1816
+ "repository": {
1817
+ "tenantId": "tenant-1",
1818
+ "repositoryId": "example/app",
1819
+ "url": "https://code.foss.global/example/app"
1820
+ },
1821
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1822
+ "source": {
1823
+ "format": "tar.gz",
1824
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
1825
+ "sizeBytes": 1024,
1826
+ "extractedSizeBytes": 4096,
1827
+ "entryCount": 4
1828
+ },
1829
+ "trigger": {
1830
+ "kind": "push",
1831
+ "ref": "refs/heads/main"
1832
+ },
1833
+ "trustClass": "trusted",
1834
+ "protectedTagRun": false,
1835
+ "secrets": []
1836
+ },
1837
+ "policy": {
1838
+ "limits": {
1839
+ "maximumSourceArchiveBytes": 1048576,
1840
+ "maximumSourceExtractedBytes": 4194304,
1841
+ "maximumSourceEntries": 1000,
1842
+ "maximumLogChunkBytes": 65536,
1843
+ "maximumTotalLogBytes": 10485760,
1844
+ "maximumWorkspaceBytes": 268435456,
1845
+ "maximumWorkspaceEntries": 100000
1846
+ },
1847
+ "ociIsolation": "container",
1848
+ "maximumTimeoutMs": 1800000
1849
+ },
1850
+ "expected": {
1851
+ "outcome": "failed",
1852
+ "code": "policy_timeout_exceeded"
1853
+ }
1854
+ },
1855
+ {
1856
+ "id": "workflow-egress-wildcard-any-depth",
1857
+ "description": "The egress entry *.example.com permits the registry host npm.internal.example.com, two labels below example.com. The workflow passes the workflow layer; the job's default timeout of 3,600,000 then exceeds the deployment maximum.",
1858
+ "workflow": {
1859
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\njobs:\n test:\n network:\n mode: egress\n allow:\n - host: '*.example.com'\n npmRead:\n - registry: https://npm.internal.example.com\n scopes:\n - '@example'\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1860
+ },
1861
+ "context": {
1862
+ "repository": {
1863
+ "tenantId": "tenant-1",
1864
+ "repositoryId": "example/app",
1865
+ "url": "https://code.foss.global/example/app"
1866
+ },
1867
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1868
+ "source": {
1869
+ "format": "tar.gz",
1870
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
1871
+ "sizeBytes": 1024,
1872
+ "extractedSizeBytes": 4096,
1873
+ "entryCount": 4
1874
+ },
1875
+ "trigger": {
1876
+ "kind": "push",
1877
+ "ref": "refs/heads/main"
1878
+ },
1879
+ "trustClass": "trusted",
1880
+ "protectedTagRun": false,
1881
+ "secrets": []
1882
+ },
1883
+ "policy": {
1884
+ "limits": {
1885
+ "maximumSourceArchiveBytes": 1048576,
1886
+ "maximumSourceExtractedBytes": 4194304,
1887
+ "maximumSourceEntries": 1000,
1888
+ "maximumLogChunkBytes": 65536,
1889
+ "maximumTotalLogBytes": 10485760,
1890
+ "maximumWorkspaceBytes": 268435456,
1891
+ "maximumWorkspaceEntries": 100000
1892
+ },
1893
+ "ociIsolation": "container",
1894
+ "maximumTimeoutMs": 1800000
1895
+ },
1896
+ "expected": {
1897
+ "outcome": "failed",
1898
+ "code": "policy_timeout_exceeded"
1899
+ }
1900
+ },
1901
+ {
1902
+ "id": "workflow-egress-wildcard-excludes-domain",
1903
+ "description": "The egress entry *.example.com does not permit the registry host example.com itself.",
1904
+ "workflow": {
1905
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\njobs:\n test:\n network:\n mode: egress\n allow:\n - host: '*.example.com'\n npmRead:\n - registry: https://example.com\n scopes:\n - '@example'\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1906
+ },
1907
+ "expected": {
1908
+ "outcome": "failed",
1909
+ "code": "npm_read_invalid"
1910
+ }
1911
+ },
1912
+ {
1913
+ "id": "workflow-npm-read-workflow-environment",
1914
+ "description": "The workflow environment defines NPM_CONFIG_USERCONFIG, which is in the merged environment of every step of an oci job with npmRead.",
1915
+ "workflow": {
1916
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\nenvironment:\n NPM_CONFIG_USERCONFIG: /tmp/npmrc\njobs:\n test:\n network:\n mode: egress\n allow:\n - host: 'npm.example.com'\n npmRead:\n - registry: https://npm.example.com\n scopes:\n - '@example'\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1917
+ },
1918
+ "expected": {
1919
+ "outcome": "failed",
1920
+ "code": "npm_read_invalid"
1921
+ }
1922
+ },
1923
+ {
1924
+ "id": "workflow-npm-read-workflow-environment-build-job",
1925
+ "description": "The workflow environment defines NPM_CONFIG_USERCONFIG and only a build job declares npmRead: the workflow environment does not apply to build jobs. The workflow passes the workflow layer; the job's default timeout of 3,600,000 then exceeds the deployment maximum.",
1926
+ "workflow": {
1927
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: candidate\n npm: none\n releaseAssets: none\nenvironment:\n NPM_CONFIG_USERCONFIG: /tmp/npmrc\njobs:\n image:\n network:\n mode: egress\n allow:\n - host: 'npm.example.com'\n npmRead:\n - registry: https://npm.example.com\n scopes:\n - '@example'\n runner:\n profile: oci-image\n labels:\n - builder\n build:\n context: .\n dockerfile: Dockerfile\n platforms:\n - linux/amd64\n"
1928
+ },
1929
+ "context": {
1930
+ "repository": {
1931
+ "tenantId": "tenant-1",
1932
+ "repositoryId": "example/app",
1933
+ "url": "https://code.foss.global/example/app"
1934
+ },
1935
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1936
+ "source": {
1937
+ "format": "tar.gz",
1938
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
1939
+ "sizeBytes": 1024,
1940
+ "extractedSizeBytes": 4096,
1941
+ "entryCount": 4
1942
+ },
1943
+ "trigger": {
1944
+ "kind": "push",
1945
+ "ref": "refs/heads/main"
1946
+ },
1947
+ "trustClass": "trusted",
1948
+ "protectedTagRun": false,
1949
+ "secrets": []
1950
+ },
1951
+ "policy": {
1952
+ "limits": {
1953
+ "maximumSourceArchiveBytes": 1048576,
1954
+ "maximumSourceExtractedBytes": 4194304,
1955
+ "maximumSourceEntries": 1000,
1956
+ "maximumLogChunkBytes": 65536,
1957
+ "maximumTotalLogBytes": 10485760,
1958
+ "maximumWorkspaceBytes": 268435456,
1959
+ "maximumWorkspaceEntries": 100000
1960
+ },
1961
+ "ociIsolation": "container",
1962
+ "maximumTimeoutMs": 1800000
1963
+ },
1964
+ "expected": {
1965
+ "outcome": "failed",
1966
+ "code": "policy_timeout_exceeded"
1967
+ }
1968
+ },
1969
+ {
1970
+ "id": "workflow-npm-read-build-secret-target",
1971
+ "description": "A build job with npmRead has the secret target NPM_CONFIG_USERCONFIG, which a build secret names.",
1972
+ "workflow": {
1973
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: candidate\n npm: none\n releaseAssets: none\njobs:\n image:\n network:\n mode: egress\n allow:\n - host: 'npm.example.com'\n npmRead:\n - registry: https://npm.example.com\n scopes:\n - '@example'\n secrets:\n - name: npm-config\n environment: NPM_CONFIG_USERCONFIG\n runner:\n profile: oci-image\n labels:\n - builder\n build:\n context: .\n dockerfile: Dockerfile\n platforms:\n - linux/amd64\n secrets:\n - id: npmconfig\n environment: NPM_CONFIG_USERCONFIG\n"
1974
+ },
1975
+ "expected": {
1976
+ "outcome": "failed",
1977
+ "code": "npm_read_invalid"
1978
+ }
1979
+ },
1980
+ {
1981
+ "id": "workflow-shared-memory-without-memory",
1982
+ "description": "A job declares sharedMemoryBytes of 8 GiB and no memoryBytes: resources_invalid compares declared values only. The workflow passes the workflow layer; the job's default timeout of 3,600,000 then exceeds the deployment maximum.",
1983
+ "workflow": {
1984
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\njobs:\n test:\n resources:\n sharedMemoryBytes: 8589934592\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n"
1985
+ },
1986
+ "context": {
1987
+ "repository": {
1988
+ "tenantId": "tenant-1",
1989
+ "repositoryId": "example/app",
1990
+ "url": "https://code.foss.global/example/app"
1991
+ },
1992
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1993
+ "source": {
1994
+ "format": "tar.gz",
1995
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
1996
+ "sizeBytes": 1024,
1997
+ "extractedSizeBytes": 4096,
1998
+ "entryCount": 4
1999
+ },
2000
+ "trigger": {
2001
+ "kind": "push",
2002
+ "ref": "refs/heads/main"
2003
+ },
2004
+ "trustClass": "trusted",
2005
+ "protectedTagRun": false,
2006
+ "secrets": []
2007
+ },
2008
+ "policy": {
2009
+ "limits": {
2010
+ "maximumSourceArchiveBytes": 1048576,
2011
+ "maximumSourceExtractedBytes": 4194304,
2012
+ "maximumSourceEntries": 1000,
2013
+ "maximumLogChunkBytes": 65536,
2014
+ "maximumTotalLogBytes": 10485760,
2015
+ "maximumWorkspaceBytes": 268435456,
2016
+ "maximumWorkspaceEntries": 100000
2017
+ },
2018
+ "ociIsolation": "container",
2019
+ "maximumTimeoutMs": 1800000
2020
+ },
2021
+ "expected": {
2022
+ "outcome": "failed",
2023
+ "code": "policy_timeout_exceeded"
2024
+ }
2025
+ },
1684
2026
  {
1685
2027
  "id": "trigger-branch-not-matched",
1686
2028
  "workflow": {
@@ -2920,6 +3262,252 @@
2920
3262
  ]
2921
3263
  }
2922
3264
  },
3265
+ {
3266
+ "id": "compile-carriage-return-line-breaks",
3267
+ "description": "Every line ends in a carriage return alone, a YAML 1.2 line break; the block scalar reads its line breaks as line feeds.",
3268
+ "workflow": {
3269
+ "source": "spec: 2.0.0\ron:\r push: {}\rpermissions:\r source: read\r artifacts: none\r caches: none\r images: none\r npm: none\r releaseAssets: none\rjobs:\r test:\r runner:\r profile: oci\r labels:\r - linux\r execution:\r profile: oci\r image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r steps:\r - run:\r - pnpm\r - test\r environment:\r NOTE: |\r x\r y\r"
3270
+ },
3271
+ "context": {
3272
+ "repository": {
3273
+ "tenantId": "tenant-1",
3274
+ "repositoryId": "example/app",
3275
+ "url": "https://code.foss.global/example/app"
3276
+ },
3277
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
3278
+ "source": {
3279
+ "format": "tar.gz",
3280
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
3281
+ "sizeBytes": 1024,
3282
+ "extractedSizeBytes": 4096,
3283
+ "entryCount": 4
3284
+ },
3285
+ "trigger": {
3286
+ "kind": "push",
3287
+ "ref": "refs/heads/main"
3288
+ },
3289
+ "trustClass": "trusted",
3290
+ "protectedTagRun": false,
3291
+ "secrets": []
3292
+ },
3293
+ "policy": {
3294
+ "limits": {
3295
+ "maximumSourceArchiveBytes": 1048576,
3296
+ "maximumSourceExtractedBytes": 4194304,
3297
+ "maximumSourceEntries": 1000,
3298
+ "maximumLogChunkBytes": 65536,
3299
+ "maximumTotalLogBytes": 10485760,
3300
+ "maximumWorkspaceBytes": 268435456,
3301
+ "maximumWorkspaceEntries": 100000
3302
+ },
3303
+ "ociIsolation": "container"
3304
+ },
3305
+ "expected": {
3306
+ "outcome": "compiled",
3307
+ "workflowDigest": "242def6036a62e9aaa48035fe6b5718f40f3b74500d2d3262dcb5ac388212f40",
3308
+ "nodes": [
3309
+ {
3310
+ "id": "test",
3311
+ "kind": "job",
3312
+ "needs": [],
3313
+ "job": {
3314
+ "spec": "2.0.0",
3315
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
3316
+ "workflowDigest": "242def6036a62e9aaa48035fe6b5718f40f3b74500d2d3262dcb5ac388212f40",
3317
+ "requirements": {
3318
+ "executionProfile": "oci",
3319
+ "isolation": "container",
3320
+ "labels": [
3321
+ "linux"
3322
+ ],
3323
+ "features": [
3324
+ "source.tar-gz"
3325
+ ],
3326
+ "limits": {
3327
+ "maximumSourceArchiveBytes": 1048576,
3328
+ "maximumSourceExtractedBytes": 4194304,
3329
+ "maximumSourceEntries": 1000,
3330
+ "maximumArtifactArchiveBytes": 0,
3331
+ "maximumArtifactExtractedBytes": 0,
3332
+ "maximumArtifactEntries": 0,
3333
+ "maximumArtifacts": 0,
3334
+ "maximumCacheArchiveBytes": 0,
3335
+ "maximumCacheExtractedBytes": 0,
3336
+ "maximumCacheEntries": 0,
3337
+ "maximumCaches": 0,
3338
+ "maximumLogChunkBytes": 65536,
3339
+ "maximumTotalLogBytes": 10485760,
3340
+ "maximumWorkspaceBytes": 268435456,
3341
+ "maximumWorkspaceEntries": 100000
3342
+ }
3343
+ },
3344
+ "permissions": {
3345
+ "source": "read",
3346
+ "artifacts": "none",
3347
+ "caches": "none",
3348
+ "images": "none"
3349
+ },
3350
+ "network": {
3351
+ "mode": "none"
3352
+ },
3353
+ "execution": {
3354
+ "profile": "oci",
3355
+ "image": "registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
3356
+ },
3357
+ "steps": [
3358
+ {
3359
+ "command": [
3360
+ "pnpm",
3361
+ "test"
3362
+ ],
3363
+ "environment": {
3364
+ "NOTE": "x\ny\n",
3365
+ "SHIPZONE_CI_MATRIX_JSON": "{}",
3366
+ "SHIPZONE_CI_INPUTS_JSON": "{}"
3367
+ }
3368
+ }
3369
+ ],
3370
+ "source": {
3371
+ "format": "tar.gz",
3372
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
3373
+ "sizeBytes": 1024,
3374
+ "extractedSizeBytes": 4096,
3375
+ "entryCount": 4
3376
+ },
3377
+ "secrets": {},
3378
+ "timeoutMs": 3600000,
3379
+ "artifacts": [],
3380
+ "caches": [],
3381
+ "compiledPlanDigest": "b68c11661ebb60182581880e4513dc08619e58d7ada5ba5c09ccd20276429b8b"
3382
+ },
3383
+ "secretBindings": {}
3384
+ }
3385
+ ]
3386
+ }
3387
+ },
3388
+ {
3389
+ "id": "compile-byte-order-mark-after-keep-block",
3390
+ "description": "A keep-chomped block scalar ends the document, followed by an empty line, a line of U+FEFF, and an empty line: the empty line before the byte order mark line is content of the scalar, the lines from the byte order mark line on are not.",
3391
+ "workflow": {
3392
+ "source": "spec: 2.0.0\non:\n push: {}\npermissions:\n source: read\n artifacts: none\n caches: none\n images: none\n npm: none\n releaseAssets: none\njobs:\n test:\n runner:\n profile: oci\n labels:\n - linux\n execution:\n profile: oci\n image: registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n steps:\n - run:\n - pnpm\n - test\n environment:\n NOTE: |+\n x\n\n\ufeff\n\n"
3393
+ },
3394
+ "context": {
3395
+ "repository": {
3396
+ "tenantId": "tenant-1",
3397
+ "repositoryId": "example/app",
3398
+ "url": "https://code.foss.global/example/app"
3399
+ },
3400
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
3401
+ "source": {
3402
+ "format": "tar.gz",
3403
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
3404
+ "sizeBytes": 1024,
3405
+ "extractedSizeBytes": 4096,
3406
+ "entryCount": 4
3407
+ },
3408
+ "trigger": {
3409
+ "kind": "push",
3410
+ "ref": "refs/heads/main"
3411
+ },
3412
+ "trustClass": "trusted",
3413
+ "protectedTagRun": false,
3414
+ "secrets": []
3415
+ },
3416
+ "policy": {
3417
+ "limits": {
3418
+ "maximumSourceArchiveBytes": 1048576,
3419
+ "maximumSourceExtractedBytes": 4194304,
3420
+ "maximumSourceEntries": 1000,
3421
+ "maximumLogChunkBytes": 65536,
3422
+ "maximumTotalLogBytes": 10485760,
3423
+ "maximumWorkspaceBytes": 268435456,
3424
+ "maximumWorkspaceEntries": 100000
3425
+ },
3426
+ "ociIsolation": "container"
3427
+ },
3428
+ "expected": {
3429
+ "outcome": "compiled",
3430
+ "workflowDigest": "fde33b8dc0ca13394f5f62ac0349b963e5678eae7cce9afa4652faada80531f6",
3431
+ "nodes": [
3432
+ {
3433
+ "id": "test",
3434
+ "kind": "job",
3435
+ "needs": [],
3436
+ "job": {
3437
+ "spec": "2.0.0",
3438
+ "sourceObjectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
3439
+ "workflowDigest": "fde33b8dc0ca13394f5f62ac0349b963e5678eae7cce9afa4652faada80531f6",
3440
+ "requirements": {
3441
+ "executionProfile": "oci",
3442
+ "isolation": "container",
3443
+ "labels": [
3444
+ "linux"
3445
+ ],
3446
+ "features": [
3447
+ "source.tar-gz"
3448
+ ],
3449
+ "limits": {
3450
+ "maximumSourceArchiveBytes": 1048576,
3451
+ "maximumSourceExtractedBytes": 4194304,
3452
+ "maximumSourceEntries": 1000,
3453
+ "maximumArtifactArchiveBytes": 0,
3454
+ "maximumArtifactExtractedBytes": 0,
3455
+ "maximumArtifactEntries": 0,
3456
+ "maximumArtifacts": 0,
3457
+ "maximumCacheArchiveBytes": 0,
3458
+ "maximumCacheExtractedBytes": 0,
3459
+ "maximumCacheEntries": 0,
3460
+ "maximumCaches": 0,
3461
+ "maximumLogChunkBytes": 65536,
3462
+ "maximumTotalLogBytes": 10485760,
3463
+ "maximumWorkspaceBytes": 268435456,
3464
+ "maximumWorkspaceEntries": 100000
3465
+ }
3466
+ },
3467
+ "permissions": {
3468
+ "source": "read",
3469
+ "artifacts": "none",
3470
+ "caches": "none",
3471
+ "images": "none"
3472
+ },
3473
+ "network": {
3474
+ "mode": "none"
3475
+ },
3476
+ "execution": {
3477
+ "profile": "oci",
3478
+ "image": "registry.example/test@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
3479
+ },
3480
+ "steps": [
3481
+ {
3482
+ "command": [
3483
+ "pnpm",
3484
+ "test"
3485
+ ],
3486
+ "environment": {
3487
+ "NOTE": "x\n\n",
3488
+ "SHIPZONE_CI_MATRIX_JSON": "{}",
3489
+ "SHIPZONE_CI_INPUTS_JSON": "{}"
3490
+ }
3491
+ }
3492
+ ],
3493
+ "source": {
3494
+ "format": "tar.gz",
3495
+ "sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
3496
+ "sizeBytes": 1024,
3497
+ "extractedSizeBytes": 4096,
3498
+ "entryCount": 4
3499
+ },
3500
+ "secrets": {},
3501
+ "timeoutMs": 3600000,
3502
+ "artifacts": [],
3503
+ "caches": [],
3504
+ "compiledPlanDigest": "5ea1d0fe4980d633960d99241348dc9818e7a1defb7edd0510c15204f0715830"
3505
+ },
3506
+ "secretBindings": {}
3507
+ }
3508
+ ]
3509
+ }
3510
+ },
2923
3511
  {
2924
3512
  "id": "compile-matrix-expansion",
2925
3513
  "description": "Expansion follows dimension declaration order (z before \"2\", the first dimension varying slowest); 0x1F, -0.0, and 1e3 resolve to 31, 0, and 1000 and \"01\" stays a string; the innermost environment level wins; limits take the largest declared maximum of each field; report needs every expansion and precedes test in plan order.",
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@ship.zone/ci-spec',
6
- version: '2.1.1',
6
+ version: '2.1.2',
7
7
  description: 'The ship.zone CI standard: language-neutral CI workflow and runner protocol specifications.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxvQkFBb0I7SUFDMUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDZGQUE2RjtDQUMzRyxDQUFBIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ship.zone/ci-spec",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "private": false,
5
5
  "description": "The ship.zone CI standard: language-neutral CI workflow and runner protocol specifications.",
6
6
  "main": "dist_ts/index.js",
package/readme.md CHANGED
@@ -12,7 +12,7 @@ The package version is the specification version, and it is the only version in
12
12
  spec: 2.0.0
13
13
  ```
14
14
 
15
- Compiled jobs and runner protocol messages carry the same `spec` field. An implementation built against version `I` accepts a declared version `D` when both have the same major version and `D` is not newer than `I`; the rule is defined in `spec/runner-protocol.md` and fixed by `conformance/version-cases.json`. Every incompatible change is a new major version, and additions arrive as new minor versions: a workflow that uses a construct introduced in a later minor version declares that version or a later one. Version 2.0.0 renames the package and its normative identifiers (see Migrating from @foss.global/ci-spec), removes the 1.x cross-version rules, and completes the compilation rules: one stable failure code per compilation failure with a fixed precedence, a stricter YAML subset, the derivation of every compiled job member, and the identities of expanded nodes. Version 2.1.0 adds job failure codes: every coherence rule of a compiled job has exactly one code, and a job that is not coherent carries the code of the first rule it violates (see Job Coherence in `spec/runner-protocol.md`); it also exports the reserved names as constants. Version 2.1.1 clarifies compilation: it states the YAML 1.2 character, byte order mark, escape, and document counting rules, bounds collection nesting to 64, and counts the 16 KiB command argument limit in UTF-8 bytes. Documents written against 2.0.0 stay valid. The history of the 1.x rules is in the changelog.
15
+ Compiled jobs and runner protocol messages carry the same `spec` field. An implementation built against version `I` accepts a declared version `D` when both have the same major version and `D` is not newer than `I`; the rule is defined in `spec/runner-protocol.md` and fixed by `conformance/version-cases.json`. Every incompatible change is a new major version, and additions arrive as new minor versions: a workflow that uses a construct introduced in a later minor version declares that version or a later one. Version 2.0.0 renames the package and its normative identifiers (see Migrating from @foss.global/ci-spec), removes the 1.x cross-version rules, and completes the compilation rules: one stable failure code per compilation failure with a fixed precedence, a stricter YAML subset, the derivation of every compiled job member, and the identities of expanded nodes. Version 2.1.0 adds job failure codes: every coherence rule of a compiled job has exactly one code, and a job that is not coherent carries the code of the first rule it violates (see Job Coherence in `spec/runner-protocol.md`); it also exports the reserved names as constants. Version 2.1.1 clarifies compilation: it states the YAML 1.2 character, byte order mark, escape, and document counting rules, bounds collection nesting to 64, and counts the 16 KiB command argument limit in UTF-8 bytes. Version 2.1.2 settles readings the text left open: separate artifact and cache name spaces, the expanded jobs and step environment names that count toward their limits, exact npm registry comparison, egress wildcards at any depth, the npm delivery names reserved in every job profile, the declared values `resources_invalid` compares, informative failure locations, carriage return line breaks, and byte order mark lines after keep-chomped block scalars. Documents written against 2.0.0 stay valid. The history of the 1.x rules is in the changelog.
16
16
 
17
17
  ## Issue Reporting and Security
18
18
 
@@ -10,6 +10,8 @@ Statements that no conformance case can observe are marked *(non-testable)*.
10
10
 
11
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
12
 
13
+ Lines end as in YAML 1.2: a line feed, a carriage return followed by a line feed, and a carriage return that no line feed follows are each one line break, and every line break in scalar content is read as a line feed. A file whose lines end in carriage returns alone therefore holds the same document as the same file with line feeds; the size limit and the raw workflow SHA-256 still count its bytes as read.
14
+
13
15
  Parsing uses YAML 1.2 with the core schema and requires:
14
16
 
15
17
  - exactly one document. As in YAML 1.2, a `---` directives end marker begins a document, and so does content that no marker precedes; a document end marker `...`, a comment, a blank line, and a byte order mark begin none. An empty file, a file of only comments or only `...` markers, and a file with a second document fail with `workflow_yaml_document_count`. A document followed by several `...` markers is one document, and a file of only `--- # comment` is one empty document, whose null value fails schema validation;
@@ -23,7 +25,7 @@ Parsing uses YAML 1.2 with the core schema and requires:
23
25
  The following YAML 1.2 rules fail with `workflow_yaml_syntax`:
24
26
 
25
27
  - Characters. A C0 control character other than tab, line feed, and carriage return is allowed nowhere, not even in a quoted scalar. DEL (U+007F), a C1 control character other than NEL (U+0080 to U+009F except U+0085), U+FFFE, U+FFFF, and U+FEFF are allowed inside a single- or double-quoted scalar, where they are content, and nowhere else, except where U+FEFF is a byte order mark. Unpaired surrogates cannot occur, because the file is well-formed UTF-8.
26
- - Byte order marks. One or more U+FEFF at the start of a line are byte order marks, which are not content, when the line comes before the one on which the document begins, is that line, or comes after a document end marker `...`. They are also byte order marks when the line comes after the document's content and it and every later line up to the end of the file, or up to the next `---` or `...` marker, are blank or comment lines: YAML 1.2 lets a document prefix follow a document without a `...` marker, and a `---` after it begins a second document. The file's own leading byte order mark is the first of them. Any other U+FEFF outside a quoted scalar, for example in a plain or block scalar, in a comment, or at the start of a line within the document or followed by content, fails.
28
+ - Byte order marks. One or more U+FEFF at the start of a line are byte order marks, which are not content, when the line comes before the one on which the document begins, is that line, or comes after a document end marker `...`. They are also byte order marks when the line comes after the document's content and it and every later line up to the end of the file, or up to the next `---` or `...` marker, are blank or comment lines: YAML 1.2 lets a document prefix follow a document without a `...` marker, and a `---` after it begins a second document. A line that begins with U+FEFF is neither an empty line nor a content line of a block scalar, so it ends a block scalar as a comment line less indented than the scalar does: after a block scalar with keep chomping (`|+` or `>+`), the empty lines before such a line are content of the scalar, and the line and every line after it are not. The file's own leading byte order mark is the first of them. Any other U+FEFF outside a quoted scalar, for example in a plain or block scalar, in a comment, or at the start of a line within the document or followed by content, fails.
27
29
  - Escapes. Every escape in a double-quoted scalar denotes a Unicode scalar value, so every string can be serialized with RFC 8785. A `\u` escape of a high surrogate (`\uD800` to `\uDBFF`) directly followed by a `\u` escape of a low surrogate (`\uDC00` to `\uDFFF`) denotes the one character the pair encodes, as in JSON: `"\uD83D\uDE00"` is U+1F600. Every other escape of a surrogate code point fails, such as a lone `\uD800` or `\uDC00` or the escape `\U0000D800`. Escapes of other characters, including control characters such as `\0` and `\x7F`, are content.
28
30
  - Nesting depth. Collections nest at most 64 deep: the root collection has depth 1, and a collection that is an entry of a sequence, or a key or value of a mapping, of depth *d* has depth *d* + 1. A collection of depth 65 fails, so a parser can stop there instead of exhausting its stack. The deepest collection of a schema-valid workflow, an alias of `publish.images[].destinations[].aliases`, has depth 8.
29
31
 
@@ -173,9 +175,9 @@ Jobs form an acyclic graph through `needs`. A dependency on a job the workflow d
173
175
 
174
176
  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.
175
177
 
176
- 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 with a command argument of more than 16,384 UTF-8 bytes, or whose command arguments total more than 131,072 UTF-8 bytes, fails with `command_limit_exceeded`; the schema bounds an argument to 16,384 characters, which can exceed 16,384 bytes.
178
+ 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`. The names a step receives are the union of the names of its merged environment, the secret targets of its job, and the reserved names (see the runner protocol's OCI Container Profile): `SHIPZONE_CI_MATRIX_JSON`, `SHIPZONE_CI_INPUTS_JSON`, and, in a job with `npmRead`, `NPM_CONFIG_USERCONFIG`. Each name counts once, however many of these sources contain it, so a declared `NPM_CONFIG_USERCONFIG` in a job with `npmRead`, which fails with `npm_read_invalid` (see npm Read Access), adds no name. A step that receives more than 128 names fails with `environment_limit_exceeded`. A step with a command argument of more than 16,384 UTF-8 bytes, or whose command arguments total more than 131,072 UTF-8 bytes, fails with `command_limit_exceeded`; the schema bounds an argument to 16,384 characters, which can exceed 16,384 bytes.
177
179
 
178
- 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.
180
+ Artifact names must be unique among the artifacts of a job, and cache names among its caches; a repeated name fails with `transfer_name_duplicate`. Artifacts and caches are separate name spaces, because a transfer is identified by its kind and its name (see the runner protocol's Transfers and Integrity), so an artifact and a cache of one job may share a name. 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.
179
181
 
180
182
  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.
181
183
 
@@ -197,7 +199,7 @@ Limits count every job of the workflow, whether or not a run skips it.
197
199
 
198
200
  ## Resources
199
201
 
200
- `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`.
202
+ `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`. The rule compares declared values only and does not apply when a job omits either of them: the `workflow` layer depends only on the workflow bytes (see Compilation Failures), and the compiled job carries only declared values (see Compiled Jobs). A `sharedMemoryBytes` above the deployment's default memory is therefore not a `workflow` layer failure; a deployment may bound it with a policy limit (see Limits), and a runner receives it only when it fits the runner's capabilities (see the runner protocol's Capabilities and Matching).
201
203
 
202
204
  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.
203
205
 
@@ -237,15 +239,15 @@ A coordinator never delivers a value that is not admissible, for example one it
237
239
 
238
240
  The following fail with `npm_read_invalid`:
239
241
 
240
- - a registry listed twice, or a scope listed under two registries;
241
- - 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;
242
- - 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.
242
+ - a registry listed twice, or a scope listed under two registries. Registries are compared as exact strings, without normalization, for example of a trailing `/` or an explicit port 443: each entry receives its own grant, which carries the entry's registry as written (see the runner protocol's npm Read Grants);
243
+ - 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. An entry permits a host as the runner protocol's Network defines: an exact entry the host itself, and a `*.` entry every name below its domain, at any depth;
244
+ - in a job with `npmRead`, whatever its profile, a secret target named `NPM_CONFIG_USERCONFIG`, a step whose merged environment defines `NPM_CONFIG_USERCONFIG`, or a build secret with id `npmrc`. The merged environment of an `oci` or `vm` step includes the workflow environment, which does not apply to build jobs (see Jobs and Steps). Both names are reserved for grant delivery, and the reserved environment name counts toward the 128 names of every step.
243
245
 
244
246
  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.
245
247
 
246
248
  ## Image Builds
247
249
 
248
- 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.
250
+ 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 per-platform build job counts toward the expanded-job ceiling; the assembly node is a coordinator node, not a job, and does not count. Build jobs have no matrix.
249
251
 
250
252
  - `context` and `dockerfile` are paths relative to the repository root; `target` selects a Dockerfile stage. Every entry of `platforms` is a `linux` platform.
251
253
  - `buildArgs` are non-secret values compiled into the plan and possibly persisted in image history. Names starting with `BUILDKIT_` are rejected.
@@ -343,7 +345,7 @@ Every job node compiles to one compiled job, a function of the workflow, the run
343
345
 
344
346
  - Workflow source: 256 KiB.
345
347
  - Jobs before expansion: 128.
346
- - Expanded jobs: 256.
348
+ - Expanded jobs: 256, counting the job nodes of the Expanded DAG and not the candidate assembly nodes.
347
349
  - Steps per job: 128.
348
350
  - Dependencies per job: 32.
349
351
  - Matrix dimensions: 8.
@@ -371,6 +373,8 @@ All objects reject unknown properties. Implementations may use lower deployment
371
373
 
372
374
  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.
373
375
 
376
+ The code is the only normative part of a failure. A coordinator may report diagnostic details with it, such as the location of a violation in the workflow and a message; they are informative, may differ between conforming coordinators, and no conformance case fixes them. When the deciding layer or rule is violated more than once, the specification does not fix which violation such details describe.
377
+
374
378
  | Layer | Code | Rule |
375
379
  | --- | --- | --- |
376
380
  | source | `workflow_too_large` | the file has more than 262,144 bytes (Parsing) |
@@ -397,9 +401,9 @@ A compilation failure fails the run before any job is enqueued and carries exact
397
401
  | workflow | `environment_conflict` | a secret target or reserved-name conflict (Jobs and Steps) |
398
402
  | workflow | `environment_limit_exceeded` | more than 128 names in a step (Jobs and Steps) |
399
403
  | workflow | `command_limit_exceeded` | a command argument above 16,384 bytes or a command above 131,072 bytes (Jobs and Steps) |
400
- | workflow | `transfer_name_duplicate` | a repeated artifact or cache name (Jobs and Steps) |
404
+ | workflow | `transfer_name_duplicate` | a name repeated among the artifacts or among the caches of a job (Jobs and Steps) |
401
405
  | workflow | `cache_path_overlap` | overlapping cache paths (Jobs and Steps) |
402
- | workflow | `resources_invalid` | `sharedMemoryBytes` above `memoryBytes` (Resources) |
406
+ | workflow | `resources_invalid` | a declared `sharedMemoryBytes` above the declared `memoryBytes` of its job (Resources) |
403
407
  | workflow | `network_host_duplicate` | a repeated egress host (Network) |
404
408
  | workflow | `npm_read_invalid` | a broken `npmRead` rule (npm Read Access) |
405
409
  | workflow | `build_input_conflict` | a broken build argument or build secret rule (Image Builds) |
@@ -276,7 +276,7 @@ Every attempt has network namespaces of its own, shared with no other attempt an
276
276
  `network` is part of the compiled job and the plan digest. Its modes are:
277
277
 
278
278
  - `none`: no traffic leaves the sandbox. This is the default: a workflow job without `network` compiles to `{ "mode": "none" }`.
279
- - `egress`: outbound access only to the listed `allow` entries. Each entry names one lowercase DNS host, either exact or `*.` followed by at least two labels, and its TCP ports. `*.example.com` matches names below `example.com` but not `example.com` itself. IP literals and single-label names are invalid.
279
+ - `egress`: outbound access only to the listed `allow` entries. Each entry names one lowercase DNS host, either exact or `*.` followed by at least two labels, and its TCP ports. `*.example.com` matches every name below `example.com`, at any depth, such as `a.example.com` and `a.b.example.com`, but not `example.com` itself. IP literals and single-label names are invalid.
280
280
 
281
281
  An empty allowlist cannot be expressed: `egress` requires at least one entry, `none` is the only representation of no network, and no implicit allowlist exists. Repository templates that need network access declare `egress` with the hosts they need.
282
282
 
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "ship.zone CI Runner Protocol",
5
- "version": "2.1.1",
5
+ "version": "2.1.2",
6
6
  "description": "Language-neutral, outbound runner protocol of @ship.zone/ci-spec. info.version is the specification version this description is written against. This draft is incompatible with the legacy internal runner protocol it replaces.",
7
7
  "license": {
8
8
  "name": "MIT",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@ship.zone/ci-spec',
6
- version: '2.1.1',
6
+ version: '2.1.2',
7
7
  description: 'The ship.zone CI standard: language-neutral CI workflow and runner protocol specifications.'
8
8
  }