Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,12 @@ evidence belongs under ignored `build/`.

`src/data/downloads.json` is a reviewed immutable catalog, not a live release
feed. Its closed schema keeps a release repository separate from an artifact
role and binds exact release evidence. Drafts, prereleases, zero-asset releases,
and guessed or mutable links fail closed; an empty catalog keeps the Classic
release-page fallback useful without JavaScript or a GitHub request.
role and binds exact release evidence, including the release coordinate and the
evidenced artifact package version. Drafts, prereleases, zero-asset releases,
and guessed or mutable links fail closed. A deliberately authorized primary
artifact-version mismatch is an explicit closed-contract exception rendered as
a warning; an empty catalog keeps the Classic release-page fallback useful
without JavaScript or a GitHub request.

Cloudflare Pages uses one Git-integrated project. `main` serves the canonical
`atrinik.org`; `www.atrinik.org` permanently redirects to it. Cloudflare builds
Expand Down
9 changes: 9 additions & 0 deletions contracts/download.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"artifactRole",
"primary",
"version",
"artifactVersion",
"releaseCoordinateException",
"tag",
"revision",
"publishedAt",
Expand Down Expand Up @@ -45,6 +47,13 @@
"type": "string",
"pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
},
"artifactVersion": {
"type": "string",
"pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
},
"releaseCoordinateException": {
"enum": ["none", "authorized-artifact-version-mismatch"]
},
"tag": {
"type": "string",
"pattern": "^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
Expand Down
14 changes: 9 additions & 5 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ structured identity and retains `noindex, nofollow`.

Downloads remain in their owning GitHub releases. Catalog schema version 2
separates the release repository from the artifact's logical role and marks at
most one deliberately reviewed primary artifact. A record binds its semantic
version/tag, 40-character source revision, publication and review times,
immutable/attested release state, asset count, platform, architecture, archive format, filename, byte size,
SHA-256, exact versioned artifact/evidence URLs, software and bundled-asset
license boundaries, compatibility, and installation guidance. Draft,
most one deliberately reviewed primary artifact. A record binds its release
semantic version/tag and evidenced artifact package version, 40-character
source revision, publication and review times, immutable/attested release
state, asset count, platform, architecture, archive format, filename, byte
size, SHA-256, exact versioned artifact/evidence URLs, software and
bundled-asset license boundaries, compatibility, and installation guidance. A
primary `v5.34.4`/`5.34.1` artifact-version mismatch is accepted only with the
exact `authorized-artifact-version-mismatch` contract value and is rendered as
a temporary warning; every other mismatch fails closed. Draft,
prerelease, zero-asset, mutable, incomplete, duplicate, or unsupported primary
records fail source validation. An empty catalog remains valid and renders the
release-page fallback without a direct download.
Expand Down
15 changes: 14 additions & 1 deletion src/components/DownloadCatalog.astro
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ const download = primaryDownload(Astro.props.catalog);
artifact was deliberately selected from a complete, published,
non-prerelease release in its owning repository.
</p>
{download.releaseCoordinateException !== "none" && (
<p class="download-coordinate-warning" role="note">
Temporary release-coordinate exception: the {download.tag}{" "}
release coordinate currently serves a Classic client package
labeled v{download.artifactVersion}. This one-off catalog entry
is authorized while the upstream publication is corrected;
verify the filename and SHA-256 below.
</p>
)}
</div>
<div class="card-actions">
<a class="button button--primary" href={download.url}>
Expand All @@ -42,9 +51,13 @@ const download = primaryDownload(Astro.props.catalog);

<dl class="download-facts">
<div>
<dt>Version</dt>
<dt>Release version</dt>
<dd>{download.version}</dd>
</div>
<div>
<dt>Artifact version</dt>
<dd>{download.artifactVersion}</dd>
</div>
<div>
<dt>Platform</dt>
<dd>
Expand Down
30 changes: 16 additions & 14 deletions src/data/downloads.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"releaseRepository": "atrinik/classic",
"artifactRole": "client",
"primary": true,
"version": "5.8.2",
"tag": "v5.8.2",
"revision": "6d5932b48a019353955d393f59cdb3fea54dd45d",
"publishedAt": "2026-08-10T00:32:43Z",
"verifiedAt": "2026-08-10T05:09:00Z",
"version": "5.34.4",
"artifactVersion": "5.34.1",
"releaseCoordinateException": "authorized-artifact-version-mismatch",
"tag": "v5.34.4",
"revision": "12b1211dd44690476c3f060d7395ccc1250c85b4",
"publishedAt": "2026-08-23T03:18:15Z",
"verifiedAt": "2026-08-24T06:51:57Z",
"draft": false,
"prerelease": false,
"immutable": true,
Expand All @@ -18,17 +20,17 @@
"platform": "windows",
"architecture": "x86_64",
"archiveFormat": "zip",
"artifact": "atrinik-classic-client-5.8.2-windows-x86_64.zip",
"bytes": 87406395,
"sha256": "1a7699f3ea5be50fc83b40dce8097c18cd0a892f98a2b0d462f851c2822689c0",
"url": "https://github.com/atrinik/classic/releases/download/v5.8.2/atrinik-classic-client-5.8.2-windows-x86_64.zip",
"releaseNotesUrl": "https://github.com/atrinik/classic/releases/tag/v5.8.2",
"manifestUrl": "https://github.com/atrinik/classic/releases/download/v5.8.2/release-manifest.json",
"checksumsUrl": "https://github.com/atrinik/classic/releases/download/v5.8.2/SHA256SUMS",
"sbomUrl": "https://github.com/atrinik/classic/releases/download/v5.8.2/atrinik-classic-5.8.2.spdx.json",
"artifact": "atrinik-classic-client-5.34.1-windows-x86_64.zip",
"bytes": 87663737,
"sha256": "06b2dcf13d1b4fe5e024dd5d921d8102c4b7521c4fcf988e654cadd7a45f2376",
"url": "https://github.com/atrinik/classic/releases/download/v5.34.4/atrinik-classic-client-5.34.1-windows-x86_64.zip",
"releaseNotesUrl": "https://github.com/atrinik/classic/releases/tag/v5.34.4",
"manifestUrl": "https://github.com/atrinik/classic/releases/download/v5.34.4/release-manifest.json",
"checksumsUrl": "https://github.com/atrinik/classic/releases/download/v5.34.4/SHA256SUMS",
"sbomUrl": "https://github.com/atrinik/classic/releases/download/v5.34.4/atrinik-classic-5.34.1.spdx.json",
"softwareLicense": "GPL-2.0-or-later",
"bundledAssetsLicense": "Bundled sound and authored assets retain their exact per-file terms; inspect the packaged LICENSE.md, ATTRIBUTIONS.md, and any license files accompanying individual assets. Use the SPDX SBOM for dependency and provenance evidence.",
"compatibility": "This client uses gameplay protocol revision 1072 and connects only to servers using the same revision; the coordinated Classic 5.8.2 server uses revision 1072.",
"compatibility": "This 5.34.1 client uses gameplay protocol revision 1079 and connects only to servers using the same revision; the v5.34.4 release line is temporarily serving this stable package.",
"installation": "Extract the ZIP, keep every packaged file and directory together, and run atrinik.exe from inside the extracted package."
}
]
Expand Down
2 changes: 2 additions & 0 deletions src/lib/downloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export interface DownloadRecord {
artifactRole: string;
primary: boolean;
version: string;
artifactVersion: string;
releaseCoordinateException: string;
tag: string;
revision: string;
publishedAt: string;
Expand Down
13 changes: 13 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,19 @@ main {
font-size: 1.08rem;
}

.download-coordinate-warning {
margin-top: 1rem;
margin-bottom: 0;
padding: 1rem 1.2rem;
border: 1px solid var(--line-gold);
background: rgba(114, 221, 255, 0.08);
color: var(--text-soft);
}

.download-coordinate-warning strong {
color: var(--text);
}

.download-facts {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
Expand Down
2 changes: 2 additions & 0 deletions tools/fixtures/download-adversarial-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"artifactRole": "client",
"primary": true,
"version": "5.9.0",
"artifactVersion": "5.9.0",
"releaseCoordinateException": "none",
"tag": "v5.9.0",
"revision": "0123456789abcdef0123456789abcdef01234567",
"publishedAt": "2026-08-10T01:00:00Z",
Expand Down
2 changes: 2 additions & 0 deletions tools/fixtures/download-valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"artifactRole": "client",
"primary": true,
"version": "1.2.3",
"artifactVersion": "1.2.3",
"releaseCoordinateException": "none",
"tag": "v1.2.3",
"revision": "0123456789abcdef0123456789abcdef01234567",
"publishedAt": "2026-01-02T03:04:05Z",
Expand Down
48 changes: 46 additions & 2 deletions tools/site-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ const versionPattern = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/u;
const releaseRepositoryPattern = /^atrinik\/[a-z][a-z0-9-]*$/u;
const artifactRolePattern = /^[a-z][a-z0-9-]*$/u;
const artifactPattern = /^[A-Za-z0-9][A-Za-z0-9._-]+$/u;
const releaseCoordinateExceptionValues = new Set([
"none",
"authorized-artifact-version-mismatch",
]);
const authorizedReleaseCoordinateException = Object.freeze({
releaseVersion: "5.34.4",
artifactVersion: "5.34.1",
});
const utcTimestampPattern =
/^(?:(?:\d{2}(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|02-(?:0[1-9]|1\d|2[0-8])))T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\dZ$/u;
const mediaPathPattern = /^\/media\/[a-z0-9][a-z0-9-]*\.([0-9a-f]{8})\.webp$/u;
Expand Down Expand Up @@ -89,6 +97,8 @@ export function validateDownload(record) {
"artifactRole",
"primary",
"version",
"artifactVersion",
"releaseCoordinateException",
"tag",
"revision",
"publishedAt",
Expand Down Expand Up @@ -127,6 +137,26 @@ export function validateDownload(record) {
record.tag !== `v${record.version}`
)
throw new Error("download version/tag mismatch");
if (
!versionPattern.test(record.artifactVersion) ||
!releaseCoordinateExceptionValues.has(record.releaseCoordinateException)
)
throw new Error("invalid download artifact version binding");
const artifactVersionMismatch = record.artifactVersion !== record.version;
const authorizedArtifactVersionMismatch =
record.primary === true &&
record.version === authorizedReleaseCoordinateException.releaseVersion &&
record.artifactVersion ===
authorizedReleaseCoordinateException.artifactVersion &&
record.releaseCoordinateException ===
"authorized-artifact-version-mismatch";
if (
(artifactVersionMismatch && !authorizedArtifactVersionMismatch) ||
(!artifactVersionMismatch && record.releaseCoordinateException !== "none")
)
throw new Error(
"download artifact version mismatch is not explicitly authorized",
);
if (
!revisionPattern.test(record.revision) ||
!sha256Pattern.test(record.sha256)
Expand Down Expand Up @@ -227,9 +257,9 @@ export function validateDownloadCatalog(catalog) {
record.archiveFormat !== "zip" ||
record.releaseAssets !== 12 ||
record.artifact !==
`atrinik-classic-client-${record.version}-windows-x86_64.zip` ||
`atrinik-classic-client-${record.artifactVersion}-windows-x86_64.zip` ||
record.sbomUrl !==
`https://github.com/atrinik/classic/releases/download/${record.tag}/atrinik-classic-${record.version}.spdx.json` ||
`https://github.com/atrinik/classic/releases/download/${record.tag}/atrinik-classic-${record.artifactVersion}.spdx.json` ||
record.softwareLicense !== "GPL-2.0-or-later",
)
)
Expand All @@ -243,7 +273,10 @@ export function validateDownloadSchemaDefinition(schema, fixture) {
schema?.additionalProperties !== false ||
schema.required?.toSorted().join("\n") !== expectedFields ||
properties.version?.pattern !== versionPatternSource ||
properties.artifactVersion?.pattern !== versionPatternSource ||
properties.tag?.pattern !== tagPatternSource ||
properties.releaseCoordinateException?.enum?.join("\n") !==
"none\nauthorized-artifact-version-mismatch" ||
new RegExp(properties.releaseRepository?.pattern ?? "").source !==
releaseRepositoryPattern.source ||
new RegExp(properties.artifactRole?.pattern ?? "").source !==
Expand Down Expand Up @@ -335,6 +368,7 @@ export function validateDownloadsPresentation(html, catalog) {
}
const requiredEvidence = [
primary.version,
primary.artifactVersion,
primary.revision,
primary.sha256,
primary.artifact,
Expand All @@ -353,12 +387,22 @@ export function validateDownloadsPresentation(html, catalog) {
throw new Error("primary download presentation omits catalog evidence");
const structuralEvidence = [
`<dt>Platform</dt><dd>${primary.platform === "windows" ? "Windows" : primary.platform} ${primary.architecture}</dd>`,
`<dt>Artifact version</dt><dd>${primary.artifactVersion}</dd>`,
`<dt>Archive</dt><dd>${primary.archiveFormat.toUpperCase()}</dd>`,
`<h3 id="install-heading">Install and compatibility</h3><p>${primary.installation}</p><p>${primary.compatibility}</p>`,
`<h3 id="license-heading">License boundary</h3><p>Classic software is licensed under <strong>${primary.softwareLicense}</strong>. ${primary.bundledAssetsLicense}</p>`,
];
if (structuralEvidence.some((value) => !html.includes(value)))
throw new Error("primary download presentation misplaces catalog evidence");
if (
primary.releaseCoordinateException !== "none" &&
!html.includes(
`Temporary release-coordinate exception: the ${primary.tag} release coordinate currently serves a Classic client package labeled v${primary.artifactVersion}.`,
)
)
throw new Error(
"primary download presentation omits its release exception",
);
if (
downloadReleaseUrls(primary).some(
(url) => !html.includes(`href="${url}"`),
Expand Down
49 changes: 49 additions & 0 deletions tools/site-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,51 @@ test("download coordinates are closed and immutable", () => {
validateDownloadSchemaDefinition(downloadSchema, validDownload),
);
assert.doesNotThrow(() => validateDownload(validDownload));
const authorizedArtifactMismatch = {
...validDownload,
version: "5.34.4",
artifactVersion: "5.34.1",
releaseCoordinateException: "authorized-artifact-version-mismatch",
tag: "v5.34.4",
artifact: "atrinik-classic-client-5.34.1-windows-x86_64.zip",
url: "https://github.com/atrinik/classic/releases/download/v5.34.4/atrinik-classic-client-5.34.1-windows-x86_64.zip",
releaseNotesUrl: "https://github.com/atrinik/classic/releases/tag/v5.34.4",
manifestUrl:
"https://github.com/atrinik/classic/releases/download/v5.34.4/release-manifest.json",
checksumsUrl:
"https://github.com/atrinik/classic/releases/download/v5.34.4/SHA256SUMS",
sbomUrl:
"https://github.com/atrinik/classic/releases/download/v5.34.4/atrinik-classic-5.34.1.spdx.json",
};
assert.doesNotThrow(() => validateDownload(authorizedArtifactMismatch));
assert.throws(
() =>
validateDownload({
...authorizedArtifactMismatch,
artifactVersion: "5.34.2",
artifact: "atrinik-classic-client-5.34.2-windows-x86_64.zip",
url: "https://github.com/atrinik/classic/releases/download/v5.34.4/atrinik-classic-client-5.34.2-windows-x86_64.zip",
sbomUrl:
"https://github.com/atrinik/classic/releases/download/v5.34.4/atrinik-classic-5.34.2.spdx.json",
}),
/explicitly authorized/u,
);
assert.throws(
() =>
validateDownload({
...validDownload,
releaseCoordinateException: "authorized-artifact-version-mismatch",
}),
/explicitly authorized/u,
);
assert.throws(
() =>
validateDownload({
...authorizedArtifactMismatch,
primary: false,
}),
/explicitly authorized/u,
);
assert.throws(
() =>
validateDownload({ ...validDownload, url: "https://example.com/latest" }),
Expand Down Expand Up @@ -240,6 +285,8 @@ test("download coordinates are closed and immutable", () => {
test("download schema and executable constraints reject the same edge forms", () => {
const rejected = [
["version", "01.2.3"],
["artifactVersion", "01.2.3"],
["releaseCoordinateException", "unreviewed-mismatch"],
["tag", "v01.2.3"],
["publishedAt", "2026-99-99T99:99:99Z"],
["publishedAt", "2026-02-31T00:00:00Z"],
Expand Down Expand Up @@ -359,6 +406,7 @@ test("download catalogs stay empty safely and reject ineligible releases", () =>
test("download presentation structurally preserves all catalog evidence", () => {
const evidence = [
validDownload.version,
validDownload.artifactVersion,
validDownload.revision,
validDownload.sha256,
validDownload.artifact,
Expand All @@ -385,6 +433,7 @@ test("download presentation structurally preserves all catalog evidence", () =>
.join(" ");
const structures = [
`<dt>Platform</dt><dd>Windows ${validDownload.architecture}</dd>`,
`<dt>Artifact version</dt><dd>${validDownload.artifactVersion}</dd>`,
`<dt>Archive</dt><dd>${validDownload.archiveFormat.toUpperCase()}</dd>`,
`<h3 id="install-heading">Install and compatibility</h3><p>${validDownload.installation}</p><p>${validDownload.compatibility}</p>`,
`<h3 id="license-heading">License boundary</h3><p>Classic software is licensed under <strong>${validDownload.softwareLicense}</strong>. ${validDownload.bundledAssetsLicense}</p>`,
Expand Down