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
71 changes: 69 additions & 2 deletions .github/workflows/snippets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,20 @@ jobs:
- name: Check extended snippets (Rust / Python / shell / JSON / TOML / YAML)
run: pnpm run check:snippets-extended

# ---------------------------------------------------------------------------
# Stellar testnet snippet validation — split into two steps so that
# deterministic content regressions block merges while provider outages
# are surfaced as annotations without blocking.
#
# Exit codes from check-stellar-testnet-snippets.ts:
# 0 – passed
# 1 – content regression → always a hard failure
# 2 – provider outage → warning annotation, does NOT block merge
# ---------------------------------------------------------------------------
stellar-testnet-snippets:
name: Stellar snippet testnet validation
runs-on: ubuntu-latest
continue-on-error: true
# No top-level continue-on-error — individual steps control leniency.
needs: extract-sdk-version
steps:
- name: Checkout
Expand Down Expand Up @@ -173,9 +183,66 @@ jobs:
run: |
echo "Testing against SDK version: ${{ needs.extract-sdk-version.outputs.sdk-version }}"

- name: Validate Stellar testnet snippets
# BLOCKING: deterministic content checks only (snippet count, file
# existence). A failure here means a real doc regression. Exit 1.
- name: Validate Stellar snippet content (blocking)
env:
MODE: content
run: pnpm run check:stellar-testnet

# NON-BLOCKING: live provider availability checks.
#
# continue-on-error keeps the job alive regardless of exit status, but it
# collapses outcome/conclusion so we cannot distinguish exit 1 from exit 2
# through those fields alone. We therefore capture the raw exit status
# explicitly and write it to GITHUB_OUTPUT before the shell exits, giving
# the next step a reliable signal to branch on.
#
# Exit codes (defined in check-stellar-testnet-snippets.ts):
# 0 – all checks passed
# 1 – content regression → hard failure, blocks merge
# 2 – provider outage → warning annotation only, does NOT block
- name: Validate Stellar testnet provider availability (non-blocking)
id: provider-check
continue-on-error: true
env:
MODE: provider
run: |
set +e
pnpm run check:stellar-testnet
STATUS=$?
set -e
echo "exit-status=$STATUS" >> "$GITHUB_OUTPUT"
exit $STATUS

# Exit 1 → content regression from a reachable provider; hard-fail.
# Exit 2 → provider outage; emit a warning annotation and continue.
# Exit 0 → all good; nothing to do.
- name: Classify provider-check exit status
if: always()
env:
PROVIDER_EXIT: ${{ steps.provider-check.outputs.exit-status }}
run: |
case "$PROVIDER_EXIT" in
0)
echo "Provider check passed."
;;
1)
echo "::error::Provider step detected a content regression (exit 1). Failing the job."
exit 1
;;
2)
echo "::warning::Provider step reported a testnet outage (exit 2). Non-blocking."
;;
*)
echo "::error::Provider step exited with unexpected status '$PROVIDER_EXIT'. Failing the job."
exit 1
;;
esac

- name: Test exit-status classification logic
run: pnpm run test:stellar-testnet

mint-validation:
name: Mint config validation and preview redirect checks
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion DEPENDENCY-POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ bar as shipped code.
| [GHSA-hqr4-qq8f-hg3x](https://github.com/advisories/GHSA-hqr4-qq8f-hg3x) | `stream-json` | `@solana/web3.js` → `jayson` | Same compatibility constraint as above. Affects the JSONC parser and verifier, which `jayson` does not use. |
| [GHSA-mjw6-4jj6-33hc](https://github.com/advisories/GHSA-mjw6-4jj6-33hc) | `stream-json` | `@solana/web3.js` → `jayson` | Same compatibility constraint as above. Affects the `Assembler` convenience stream, which `jayson` does not use. |

The high-severity Stellar path findings (`axios`, `toml`) and the Solana path
The high-severity Stellar path findings (`axios`, `follow-redirects`, `toml`) and the Solana path
`uuid` finding are fixed by the `pnpm.overrides` entries in `package.json`; see
the pull request that added them for the resolution details.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"test:contract-registry": "node --test scripts/check-contract-registry.test.mjs",
"test:preview-redirect": "node --test scripts/check-preview-redirect.test.mjs",
"check:stellar-testnet": "tsx scripts/check-stellar-testnet-snippets.ts",
"test:stellar-testnet": "node --test scripts/check-stellar-testnet-snippets.test.mjs",
"generate:stellar-reference": "tsx scripts/generate-stellar-reference.ts",
"check:stellar-reference": "tsx scripts/generate-stellar-reference.ts --check",
"generate:playground-fixtures": "node scripts/playground/generate-fixtures.mjs",
Expand Down
115 changes: 115 additions & 0 deletions scripts/check-stellar-testnet-snippets.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* check-stellar-testnet-snippets.test.mjs
*
* Verifies the exit-status classification logic that the CI workflow relies on
* to distinguish a content regression (exit 1, blocks merge) from a provider
* outage (exit 2, non-blocking warning).
*
* The workflow step "Classify provider-check exit status" uses a shell `case`
* statement that reads the captured exit status from GITHUB_OUTPUT. These
* tests replicate that logic in JS so it can be run with `node --test` without
* requiring a live runner environment, providing the regression-test coverage
* requested in https://github.com/wraith-protocol/docs/issues/187.
*/

import test from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";

// ---------------------------------------------------------------------------
// Mirror of the workflow's case-statement classification logic.
// ---------------------------------------------------------------------------

/**
* Classifies a provider-step exit status the same way the CI workflow does.
*
* Returns:
* { action: "pass" } – exit 0, nothing to do
* { action: "fail", reason } – exit 1 or unexpected, job should fail
* { action: "warn", reason } – exit 2, non-blocking warning
*
* @param {number | string} exitStatus
*/
function classifyProviderExit(exitStatus) {
const code = Number(exitStatus);
switch (code) {
case 0:
return { action: "pass" };
case 1:
return {
action: "fail",
reason: "Provider step detected a content regression (exit 1). Failing the job.",
};
case 2:
return {
action: "warn",
reason: "Provider step reported a testnet outage (exit 2). Non-blocking.",
};
default:
return {
action: "fail",
reason: `Provider step exited with unexpected status '${exitStatus}'. Failing the job.`,
};
}
}

// ---------------------------------------------------------------------------
// Classification unit tests
// ---------------------------------------------------------------------------

test("exit 0 → pass, job continues normally", () => {
const result = classifyProviderExit(0);
assert.equal(result.action, "pass");
});

test("exit 1 → fail, content regression blocks the job", () => {
const result = classifyProviderExit(1);
assert.equal(result.action, "fail");
assert.match(result.reason, /content regression/);
});

test("exit 2 → warn, provider outage is non-blocking", () => {
const result = classifyProviderExit(2);
assert.equal(result.action, "warn");
assert.match(result.reason, /outage/);
});

test("unexpected exit code → fail, not silently swallowed", () => {
for (const code of [3, 127, 255, -1]) {
const result = classifyProviderExit(code);
assert.equal(result.action, "fail", `expected fail for exit code ${code}`);
assert.match(result.reason, /unexpected status/);
}
});

test("exit 1 and exit 2 produce different actions (core invariant)", () => {
const regression = classifyProviderExit(1);
const outage = classifyProviderExit(2);
assert.notEqual(
regression.action,
outage.action,
"exit 1 (content regression) and exit 2 (provider outage) must be classified differently",
);
});

// ---------------------------------------------------------------------------
// Integration smoke test: the TypeScript script itself exits with the right
// codes when run in content mode against the real docs file.
// ---------------------------------------------------------------------------

test("MODE=content exits 0 when stellar.mdx has enough snippets", () => {
const result = spawnSync(
"pnpm",
["run", "check:stellar-testnet"],
{
cwd: new URL("..", import.meta.url).pathname,
env: { ...process.env, MODE: "content" },
encoding: "utf8",
},
);
assert.equal(
result.status,
0,
`expected exit 0 in content mode, got ${result.status}.\nstdout: ${result.stdout}\nstderr: ${result.stderr}`,
);
});
Loading
Loading