Skip to content
Merged
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,12 @@ jobs:
# nobody can rely on at the moment it matters.
run: python3 scripts/test_check_release_gate.py

- name: The triage-SLA measurement counts neither weekends nor labels
# The daily measurement itself lives in triage-sla.yml, because whether
# an issue got a reply is not a property of a PR. Its rules are, and a
# change that breaks them should fail here, not on some later weekday.
run: python3 scripts/test_triage_sla.py

- name: LINT-RATCHET — the clippy warning count may not rise
id: lint_ratchet
# Runs even if an earlier gate failed, and the run-all-gates step
Expand Down Expand Up @@ -1052,6 +1058,28 @@ jobs:
# listed in `needs` below, because a suite added without being added here would
# stop gating silently: the aggregator would go green without ever waiting for
# it.
triage-sla-contract:
name: triage SLA role lookup (workflow token)
runs-on: ubuntu-latest
timeout-minutes: 5
# Match the scheduled SLA workflow. Metadata read is implicit in GitHub's
# repository token; the permission endpoint must not need a maintainer PAT.
permissions:
contents: read
issues: read
steps:
- uses: actions/checkout@v4
- name: Offline SLA rules
run: python3 scripts/test_triage_sla.py
- name: Check actual workflow-token role classification
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ROLE_MAINTAINER: ${{ vars.TRIAGE_SLA_MAINTAINER || 'Raghuvarma905' }}
ROLE_OUTSIDE_USER: ${{ vars.TRIAGE_SLA_OUTSIDE_USER || 'octocat' }}
run: >-
python3 scripts/check_triage_role_lookup.py --repo "$GITHUB_REPOSITORY"
--maintainer "$ROLE_MAINTAINER" --outside-user "$ROLE_OUTSIDE_USER"

ci-ok:
name: ci-ok
# always() is load-bearing. Without it a failed dependency SKIPS this job, and
Expand All @@ -1060,6 +1088,7 @@ jobs:
if: always()
needs:
- changes
- triage-sla-contract
- supply-chain
- test
- docs-gate
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/triage-sla.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Triage SLA

# Measures the promise SUPPORT.md makes (DX-11, #1785): an issue opened by
# someone without triage access gets a first response from a maintainer within
# 2 business days. See scripts/triage_sla.py for exactly what counts.
#
# Daily, not per-PR. Whether the promise was kept depends on the issue tracker,
# not on the code in a pull request, so it has no business blocking a merge. A
# run fails when an issue opened since the policy took effect missed it, and the
# job summary carries the table either way.
#
# Exit 2 means "could not measure" (the API was unreachable, or the permission
# lookup found no maintainers at all) -- a broken measurement, not a missed SLA.
#
# Scheduled workflows run from the default branch only, so this starts once it
# reaches main.

on:
schedule:
- cron: "30 3 * * 1-5" # 09:00 Asia/Kolkata, Monday to Friday
workflow_dispatch:

permissions:
contents: read
issues: read

jobs:
first-response:
name: first response within 2 business days
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Rules of the measurement
run: python3 scripts/test_triage_sla.py
- name: Measure the issue tracker
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python3 scripts/triage_sla.py --repo "$GITHUB_REPOSITORY"
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ Small, well-scoped PRs are reviewed and merged faster than large ones.
| **CI** (`ci.yml`) | every PR, push to `main` | `cargo test --workspace` (dev profile) and `cargo check --all-targets` | ~7 min |
| **Nightly sweep** (`nightly.yml`) | 02:30 UTC, or manual dispatch | `scripts/verify-sweep.sh` — tests, every example, every bench, every case study | ~50 min |
| **GPU CI** (`gpu-ci.yml`) | manual dispatch only | GPU-path tests on a self-hosted runner | — |
| **Triage SLA** (`triage-sla.yml`) | 03:30 UTC on weekdays, or manual dispatch | `scripts/triage_sla.py` — fails if an outside issue waited more than 2 business days for a first response ([SUPPORT.md](SUPPORT.md)) | ~1 min |

**`test (ubuntu-latest)` is a required check on `main`.** A PR cannot merge until
it passes.
Expand Down Expand Up @@ -228,7 +229,8 @@ Good first contributions, roughly easiest to hardest:
4. **Open issues** — see the
[issue tracker](https://github.com/samyama-ai/samyama-graph/issues), where
each issue is a concrete unit of work, and known gaps noted in
`docs/CYPHER-COMPATIBILITY.md`.
`docs/CYPHER-COMPATIBILITY.md`. [`ROADMAP.md`](ROADMAP.md) explains which
of them are accepted for a date.

## Project Layout

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ Contributions are welcome — bug reports, docs, tests, and code. See
**[CONTRIBUTING.md](CONTRIBUTING.md)** for setup, build/test commands and the
pull request workflow. Found a bug? [Open an issue](https://github.com/samyama-ai/samyama-graph/issues/new/choose).
Questions? [Join the community chat](https://chat.whatsapp.com/Jjjkb3uWRDi1YMdfffaD9d).
Every issue gets a first response within 2 business days ([SUPPORT.md](SUPPORT.md));
what is accepted for a date is in [ROADMAP.md](ROADMAP.md).

## License

Expand Down
42 changes: 42 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Roadmap

This page says what is accepted and how something gets there. It does not list
the work itself: a list copied into a file is out of date the week after it is
written, which is how the previous `ROADMAP.md` became a development history and
was removed (#1835). The issue tracker is the list, and the links below are live.

## Accepted

Accepted work is an open issue labelled with a **horizon**.

| Horizon | Due | Issues |
|---|---|---|
| **H1** | 2026-12-31 | [open](https://github.com/samyama-ai/samyama-graph/issues?q=is%3Aissue+is%3Aopen+label%3AH1) · [done](https://github.com/samyama-ai/samyama-graph/issues?q=is%3Aissue+is%3Aclosed+label%3AH1+reason%3Acompleted) |

Most H1 issues are named after a requirement in the product specification
(`API-09`, `DX-11`, …) and say in their body what has to exist for the
requirement to be met. Issues without a horizon label are not yet committed to
any date. An issue being open is not, by itself, a commitment.

## How a request is accepted, or not

Open an issue. Within 2 business days a maintainer responds
([SUPPORT.md](SUPPORT.md)). The answer is one of:

- **accepted**: it gets a horizon label;
- **needs more information**: the issue stays open and says what is missing;
- **duplicate**: it is closed, pointing at the issue it duplicates;
- **not planned**: it is closed as *not planned*, with the reason in the issue.

## Not planned

There is no published list of non-goals yet. Until there is, the way to find
out whether something is out of scope is to ask in an issue, and the answer,
with its reason, will be on that issue.

From the publication of this page, a declined request is closed as
[not planned](https://github.com/samyama-ai/samyama-graph/issues?q=is%3Aissue+is%3Aclosed+reason%3A%22not+planned%22).
Before it, that reason was used for other things (work split into another
issue, a defect fixed some other way), so the older entries there are not
declined requests. Design alternatives considered and rejected are recorded in
the [architecture decision records](docs/ADR/).
55 changes: 55 additions & 0 deletions SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Getting help

## Where to go

| You have | Go to |
|---|---|
| A bug, or a feature request | [Open an issue](https://github.com/samyama-ai/samyama-graph/issues/new/choose) |
| A question about using Samyama | The [community chat](https://chat.whatsapp.com/Jjjkb3uWRDi1YMdfffaD9d), or the [book](https://graph.samyama.cloud/book/) first |
| A security vulnerability | **Not an issue.** Follow [SECURITY.md](SECURITY.md), which has its own response times |
| A question about what is planned | [ROADMAP.md](ROADMAP.md) |

## What to expect when you open an issue

**A maintainer responds to every issue within 2 business days.**

- **A response** is a comment from a maintainer, including the explanation
accompanying a closure. Closing without an explanation does not count.
Adding a label or an assignee does not count: GitHub does not
tell you when that happens, so from where you sit nothing has happened.
- **Business days** are Monday to Friday, in India Standard Time (Asia/Kolkata),
where the maintainers are. Public holidays are not excluded.
- **A maintainer** is anyone with triage access to this repository or higher.
- It is a promise about a **first response**, not about a fix or a release date.
The first response says what happens next: we need more information, it is
accepted (and labelled with a horizon, see [ROADMAP.md](ROADMAP.md)), it is a
duplicate, or it is not something we will do, and why.

It covers issues in this repository. It does not cover pull requests, the
community chat, or security reports, which [SECURITY.md](SECURITY.md) handles
under a separate promise: acknowledgement within 3 business days, triage within 7.

## How it is measured

[`scripts/triage_sla.py`](scripts/triage_sla.py) reads the issue tracker and
reports the first-response time of every issue opened by someone outside the
maintainer team. The [Triage SLA workflow](.github/workflows/triage-sla.yml) runs
it every weekday and fails when an issue opened since this page was published
missed the 2 days. Its job summary is the current record. You can run it
yourself. The policy starts when this page is first merged into the default
branch:

GITHUB_TOKEN=$(gh auth token) python3 scripts/triage_sla.py

## The record before this page

Until this page was published there was no promise, and the two issues opened
from outside the team both waited far longer than 2 business days:

| Issue | Opened | First response | Business days |
|---|---|---|---:|
| [#275](https://github.com/samyama-ai/samyama-graph/issues/275) | 2026-07-02 | a comment, 2026-09-30 | 64 |
| [#542](https://github.com/samyama-ai/samyama-graph/issues/542) | 2026-08-17 | closed 2026-09-30 without an explanatory response | -- |

They are listed here because a promise is only worth what its record shows, and
the record starts on the day this page was published.
12 changes: 10 additions & 2 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

This directory holds the checks used by CI and release workflows, local
verification tools, and dataset helpers. The automated scripts are called from
`.github/workflows/ci.yml`, `.github/workflows/nightly.yml`, and
`.github/workflows/tag-release.yml`.
`.github/workflows/ci.yml`, `.github/workflows/nightly.yml`,
`.github/workflows/tag-release.yml`, and `.github/workflows/triage-sla.yml`.

## Automated checks

Expand All @@ -15,6 +15,8 @@ verification tools, and dataset helpers. The automated scripts are called from
| `check_thread_determinism.sh` | Compares openCypher TCK results across thread counts. CI supplies the TCK feature directory. | `scripts/check_thread_determinism.sh /path/to/tck/features` |
| `test_check_release_gate.py` | Tests the release-gate validator. | `python3 scripts/test_check_release_gate.py` |
| `check_release_gate.py` | Blocks a release tag when its CH-REGRESS verdict is missing, stale, red, or does not cover the commit being tagged. | `python3 scripts/check_release_gate.py RELEASE-GATE.json --commit SHA` |
| `triage_sla.py` | Reports the first-response time of every issue opened from outside the maintainer team, and fails if one opened since `SUPPORT.md` was published waited more than 2 business days. `triage-sla.yml` runs it on weekdays; it needs a token. | `GITHUB_TOKEN=$(gh auth token) python3 scripts/triage_sla.py` |
| `test_triage_sla.py` | Tests the triage-SLA rules offline: business days, explained responses, and delayed policy publication through a merge. | `python3 scripts/test_triage_sla.py` |
| `verify-sweep.sh` | Runs tests, examples, benchmarks, and case studies, recording a result for each. The nightly workflow runs it. | `scripts/verify-sweep.sh --only tests` |

The full sweep accepts `--provision`, `--out DIR`, `--jobs N`, and repeatable
Expand Down Expand Up @@ -65,3 +67,9 @@ The shell scripts require Bash. Python tools use the Python 3 standard library;
checks that build or run Samyama need the repository's Rust toolchain. Dataset
helpers need network access; the Graphalytics S-size download also needs
`zstd`. Coverage installs `cargo-llvm-cov` if needed.

The `triage SLA role lookup (workflow token)` CI job also checks the role API
with the same read permissions as the scheduled SLA workflow. Its read-only
fixtures default to maintainer `Raghuvarma905` and outside user `octocat`.
If their roles change, update repository variables `TRIAGE_SLA_MAINTAINER` and
`TRIAGE_SLA_OUTSIDE_USER`. The token stays in the environment and is never printed.
33 changes: 33 additions & 0 deletions scripts/check_triage_role_lookup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Exercise SLA role classification with the workflow's own GitHub token.

The fixtures must name a current repository maintainer and a public user without
triage access. This performs two read-only permission lookups, not a tracker scan.
"""
import argparse
import os

from triage_sla import GitHub


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--repo', required=True)
parser.add_argument('--maintainer', required=True)
parser.add_argument('--outside-user', required=True)
args = parser.parse_args()
token = os.environ.get('GITHUB_TOKEN')
if not token:
parser.error('GITHUB_TOKEN is required; this check must exercise authenticated access')
if args.maintainer.casefold() == args.outside_user.casefold():
parser.error('role fixtures must be different users')
gh = GitHub(args.repo, token)
if not gh.is_maintainer(args.maintainer):
raise SystemExit(f'expected {args.maintainer} to resolve as a maintainer')
if gh.is_maintainer(args.outside_user):
raise SystemExit(f'expected {args.outside_user} to resolve without triage access')
print('PASS: workflow token distinguishes the maintainer and outside-user fixtures')


if __name__ == '__main__':
main()
Loading
Loading