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
11 changes: 11 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Soroban rejects wasm32-unknown-unknown on Rust 1.82+: that target silently
# enables reference-types and multi-value, which the Soroban environment does not
# support and which cannot easily be turned off. wasm32v1-none is the supported
# target from Rust 1.84 onward.
[build]
target = "wasm32v1-none"

[alias]
# Tests need the host, not wasm — `cargo test` alone would try to run the
# wasm32v1-none binaries.
t = "test --target aarch64-apple-darwin"
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Name it, say why, and say what it replaces. -->
- [ ] Test suite passes locally
- [ ] This PR does one thing
- [ ] No keys, secrets, or `.env` files in the diff
- [ ] I've read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) and agree to license this under Apache-2.0
- [ ] I've read [CONTRIBUTING.md](https://github.com/StelFlow-labs/StelFlow/blob/main/docs/CONTRIBUTING.md) and agree to license this under Apache-2.0

## Open questions for the reviewer

Expand Down
107 changes: 107 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# StelFlow label taxonomy.
#
# Four namespaces, applied in this order of usefulness: `area:` on everything,
# `type:` on everything, `status:` only when it says something the issue state
# does not, and `difficulty:` only on issues genuinely open to newcomers.
#
# The rule that keeps this usable: **a label nobody can apply consistently is
# worse than no label**. Every entry below has a stated test for when it applies.
# If you find yourself hesitating between two, the taxonomy is wrong — open an
# issue rather than guessing.
#
# Apply with: gh label create --file .github/labels.yml (or the sync action)

# ---------------------------------------------------------------------------
# area: — which part of the system. Exactly one per issue.
# ---------------------------------------------------------------------------

- name: "area: contract"
color: "0B5FFF"
description: "Soroban contract — accrual, storage, authorization, events"

- name: "area: web"
color: "1F6FEB"
description: "Next.js dashboard and anything it renders"

- name: "area: sdk"
color: "3B82F6"
description: "Generated bindings and the TypeScript client surface"

- name: "area: indexer"
color: "60A5FA"
description: "Off-chain event ingestion — not built yet, see docs/indexer-design.md"

- name: "area: docs"
color: "8B949E"
description: "Anything under docs/, plus README"

- name: "area: tooling"
color: "6E7681"
description: "CI, build, linting, developer setup"

# ---------------------------------------------------------------------------
# type: — what kind of work. Exactly one per issue.
# ---------------------------------------------------------------------------

- name: "type: bug"
color: "D1242F"
description: "Something behaves differently from what the docs or tests say"

- name: "type: feature"
color: "0E8A16"
description: "New capability that does not exist today"

- name: "type: research"
color: "A371F7"
description: "Produces a written finding, not code. Cite sources."

- name: "type: security"
color: "B60205"
description: "Threat model, invariants, or anything that can strand or lose funds"

- name: "type: chore"
color: "586069"
description: "Maintenance with no behaviour change"

# ---------------------------------------------------------------------------
# status: — apply only when it tells a reader something the open/closed state
# does not. An ordinary open issue needs none of these.
# ---------------------------------------------------------------------------

- name: "status: needs-design"
color: "FBCA04"
description: "Cannot be implemented until a decision is written down"

- name: "status: blocked"
color: "E99695"
description: "Waiting on another issue or an external dependency. Say which in a comment."

- name: "status: ready"
color: "0E8A16"
description: "Scope is settled and someone could start today"

- name: "status: in-progress"
color: "1D76DB"
description: "Someone is actively on it. Assign yourself when you add this."

# ---------------------------------------------------------------------------
# difficulty: — one tier, not a ladder.
#
# `good first issue` is kept because GitHub surfaces it in its own discovery
# UI, which a custom `difficulty:` label does not. Rather than run both
# schemes, there is exactly one tier above it. Three tiers would need a
# consistent boundary between "medium" and "hard", and nobody applies that
# consistently — including me.
# ---------------------------------------------------------------------------

- name: "good first issue"
color: "7057FF"
description: "Self-contained, no prior context needed, reviewer will help"

- name: "deep dive"
color: "5319E7"
description: "Needs real context on the design. Read the linked docs before starting."

- name: "help wanted"
color: "008672"
description: "Maintainer is not working on this and would welcome someone who is"
62 changes: 62 additions & 0 deletions .github/workflows/contract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Contract CI.
#
# The contract is non-upgradeable: a bug that reaches mainnet cannot be patched
# out. That makes this pipeline the last place a regression can be caught
# cheaply, so it gates on formatting, lints, tests, and a real Wasm build rather
# than just "cargo test".
name: contract

on:
push:
branches: [main]
paths: ["contracts/**", "Cargo.toml", "Cargo.lock", ".github/workflows/contract.yml"]
pull_request:
paths: ["contracts/**", "Cargo.toml", "Cargo.lock", ".github/workflows/contract.yml"]

env:
CARGO_TERM_COLOR: always
# Soroban rejects wasm32-unknown-unknown on Rust 1.82+ — that target enables
# reference-types and multi-value, which the host does not support.
WASM_TARGET: wasm32v1-none

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --component rustfmt,clippy
rustup target add "$WASM_TARGET"

- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0

- name: Formatting
run: cargo fmt --all --check

# Tests run on the host: the testutils feature does not build for wasm.
- name: Tests
run: cargo test --target x86_64-unknown-linux-gnu --all-features

- name: Clippy (host, all targets)
run: cargo clippy --target x86_64-unknown-linux-gnu --all-targets -- -D warnings

- name: Clippy (wasm lib)
run: cargo clippy --target "$WASM_TARGET" --release --lib -- -D warnings

- name: Build Wasm
run: cargo build --target "$WASM_TARGET" --release

# A contract too large to deploy is a failure worth catching here rather
# than at deploy time. The ceiling is generous; the check exists to make an
# unexpected jump visible in the diff that caused it.
- name: Report Wasm size
run: |
size=$(stat -c%s target/"$WASM_TARGET"/release/stelflow.wasm)
echo "stelflow.wasm: $size bytes"
echo "### Contract size: \`$size\` bytes" >> "$GITHUB_STEP_SUMMARY"
if [ "$size" -gt 200000 ]; then
echo "::error::Wasm exceeded 200 KB — investigate before merging."
exit 1
fi
64 changes: 64 additions & 0 deletions .github/workflows/docs-site.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Build and publish the docs site to GitHub Pages.
#
# DELIBERATELY NOT ENABLED. `workflow_dispatch` means it only ever runs when a
# maintainer clicks "Run workflow"; there is no push trigger. Issue #13 asked for
# the deploy config to exist without turning deployment on, because enabling
# Pages is a repo-settings decision that belongs to whoever owns the repo, not to
# a pull request.
#
# To turn it on:
# 1. Settings → Pages → Source: "GitHub Actions"
# 2. Add the `push` trigger below.
name: docs site

on:
workflow_dispatch:
# Uncomment to publish on every push to main:
# push:
# branches: [main]
# paths: ["docs/**", "assets/**", ".github/workflows/docs-site.yml"]

permissions:
contents: read
pages: write
id-token: write

# One deploy at a time. `cancel-in-progress: false` so a queued deploy finishes
# rather than being dropped — a half-published docs site is worse than a late one.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
# VitePress reads git history for each page's "last updated" stamp.
fetch-depth: 0

- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0

- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 22
cache: pnpm

- run: pnpm install --frozen-lockfile
- run: pnpm docs:build

- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
- uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: docs/.vitepress/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
56 changes: 56 additions & 0 deletions .github/workflows/web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Web app and docs site CI.
#
# The bindings are generated rather than committed, so this workflow cannot type
# check the app without them. It regenerates from the deployed contract, which
# also makes the job a live check that `deployments.json` still points at
# something real.
name: web

on:
push:
branches: [main]
paths: ["apps/**", "docs/**", "package.json", "pnpm-lock.yaml", ".github/workflows/web.yml"]
pull_request:
paths: ["apps/**", "docs/**", "package.json", "pnpm-lock.yaml", ".github/workflows/web.yml"]

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # VitePress reads history for per-page "last updated"

- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0

- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 22
cache: pnpm

# Pinned rather than "latest": a CLI upgrade can change the shape of the
# generated bindings, and that should show up as a deliberate diff rather
# than as an unrelated PR going red. Matches the version this was developed
# against.
- name: Install Stellar CLI
env:
STELLAR_CLI_VERSION: 27.1.0
run: |
curl -sSfL \
"https://github.com/stellar/stellar-cli/releases/download/v${STELLAR_CLI_VERSION}/stellar-cli-${STELLAR_CLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz" \
| tar -xz -C /usr/local/bin stellar
stellar --version

- run: pnpm install --frozen-lockfile

# Regenerating here rather than committing the output keeps the checked-in
# tree from drifting away from the Wasm actually on chain. If this step
# fails, deployments.json is pointing at a contract that no longer answers.
- name: Generate contract bindings
run: pnpm bindings

- run: pnpm install --no-frozen-lockfile
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm build
- run: pnpm docs:build
27 changes: 26 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ node_modules/
dist/
build/
out/
lib/
*.tsbuildinfo
.eslintcache

# `lib/` was listed above as a build-output directory. It silently swallowed
# apps/web/lib/ — the app's entire source tree — and nothing local noticed,
# because the files were on disk. Only CI type-checking a clean checkout caught
# it. Removed rather than negated: nothing in this repo emits to lib/, and a
# rule that hides source is far worse than an unignored directory that never
# gets created.

# Generated contract bindings — regenerated from the contract spec in CI,
# so a stale checked-in copy is worse than none.
packages/*/src/generated/
Expand Down Expand Up @@ -91,3 +97,22 @@ Thumbs.db
tmp/
.tmp/
scratch/

# Soroban test snapshots — regenerated by `cargo test`, large, and not read by
# humans. The assertions in src/tests/ are the actual specification.
test_snapshots/

# Generated TypeScript bindings. Rebuilt from the deployed contract by
# `pnpm bindings`; committing them would let the checked-in copy drift from the
# Wasm actually on chain.
packages/stelflow-sdk/

# Next.js
.next/
next-env.d.ts
apps/web/AGENTS.md
apps/web/CLAUDE.md

# VitePress
docs/.vitepress/dist/
docs/.vitepress/cache/
26 changes: 24 additions & 2 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,34 @@
// tagging them "text" would be a language lie, and every shell snippet elsewhere in the repo
// is already tagged `bash`. Not worth a mixed-motive rule to catch untagged fences that are
// untagged on purpose.
"MD040": false
"MD040": false,

// The README banner has to be a <picture> element. A README image renders inside an <img>,
// which has no inherited text colour, so a single currentColor asset resolves to black and
// disappears on GitHub's dark theme — the two-asset + prefers-color-scheme route is the only
// one that works, and it is what issue #10 asks for. This allowlist is exactly the elements
// that needs and nothing more, so stray HTML elsewhere in the prose is still caught.
"MD033": { "allowed_elements": ["p", "picture", "source", "img"] },

// Follows from the same banner: the README's first line is that <picture> block rather than
// the H1, which sits directly beneath it.
"MD041": { "allow_preamble": true }
},

"globs": ["**/*.md"],

// GitHub PR/issue templates intentionally open with an HTML comment instructing the author,
// not a heading — that's how GitHub's "New PR"/"New issue" UI expects them to read.
"ignores": ["**/node_modules/**", "**/.git/**", ".github/PULL_REQUEST_TEMPLATE.md", ".github/ISSUE_TEMPLATE/**"]
"ignores": [
"**/node_modules/**",
"**/.git/**",
".github/PULL_REQUEST_TEMPLATE.md",
".github/ISSUE_TEMPLATE/**",

// Generated by `pnpm bindings` and gitignored. Linting a file the toolchain
// rewrites on every run means fixing the same findings forever.
"packages/stelflow-sdk/**",
"docs/.vitepress/dist/**",
"docs/.vitepress/cache/**"
]
}
Loading
Loading