diff --git a/sentry/github/README.md b/sentry/github/README.md new file mode 100644 index 0000000..b68a759 --- /dev/null +++ b/sentry/github/README.md @@ -0,0 +1,82 @@ +# As Decided Sentry + +Deterministic code enforcement for engineering decisions. + +Sentry checks machine-readable constraints carried by your decision artifacts +against the source changes in a pull request. It supports required and +forbidden patterns, import boundaries, full-tree certification, and SARIF +annotations—without embeddings, network calls, or an LLM judge. + +## Use it + +```yaml +name: Sentry + +on: + pull_request: + +permissions: + contents: read + security-events: write + +jobs: + sentry: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: asdecided/sentry@v0.26.0 +``` + +By default, Sentry checks added lines against the pull request base and uploads +one SARIF report to GitHub Code Scanning. + +Use `full: "true"` to certify the complete repository tree: + +```yaml + - uses: asdecided/sentry@v0.26.0 + with: + full: "true" +``` + +## Decision constraints + +Add a `Code Constraints` block to an accepted decision: + +```yaml +version: 1 +eligibility: eligible +reason: "The dependency boundary is deterministic and repository-local." +rules: + - id: domain-has-no-database-driver + kind: forbid_import + path_glob: "src/domain/**/*.rs" + pattern: "^(sqlx|diesel)(::|$)" + message: "Domain code must not import a database driver." +``` + +Supported rule kinds: + +- `forbid_import` +- `forbid_pattern` +- `require_pattern` + +Sentry deliberately has no model-judged escape hatch. A decision is either +enforced through a deterministic rule, explicitly ineligible, or visibly +unclassified. + +## Inputs + +| Input | Default | Purpose | +| --- | --- | --- | +| `path` | `decisions` | Decision corpus directory | +| `repository` | `.` | Source repository root | +| `base` | PR base SHA | Git base revision for diff mode | +| `full` | `false` | Check the complete tree | +| `upload-sarif` | `true` | Upload Code Scanning results | +| `sarif-file` | `asdecided-sentry.sarif` | SARIF output path | +| `asdecided-version` | `0.26.0` | Native release to install | + +Authoritative development lives in +[asdecided/ci](https://github.com/asdecided/ci/tree/main/sentry). diff --git a/sentry/github/action.yml b/sentry/github/action.yml index d7a9d10..173bcc5 100644 --- a/sentry/github/action.yml +++ b/sentry/github/action.yml @@ -39,7 +39,7 @@ inputs: asdecided-version: description: "Verified native asdecided-core release to install." required: false - default: "0.24.0" + default: "0.26.0" outputs: exit-code: @@ -55,40 +55,39 @@ runs: bash "$GITHUB_ACTION_PATH/../../shared/install-native.sh" \ "${{ inputs.asdecided-version }}" - - name: Resolve enforcement mode - shell: bash - env: - INPUT_BASE: ${{ inputs.base }} - INPUT_FULL: ${{ inputs.full }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} - run: | - if [ "$INPUT_FULL" = "true" ]; then - echo "SENTRY_MODE=--full" >> "$GITHUB_ENV" - exit 0 - fi - BASE="$INPUT_BASE" - if [ -z "$BASE" ]; then - BASE="$PR_BASE_SHA" - fi - if [ -z "$BASE" ]; then - echo "::error::Sentry needs a base revision outside a pull_request event; set the base input or use full=true." - exit 2 - fi - echo "SENTRY_MODE=--base $BASE" >> "$GITHUB_ENV" - - name: Run As Decided Sentry id: sentry shell: bash env: + INPUT_BASE: ${{ inputs.base }} + INPUT_FULL: ${{ inputs.full }} INPUT_PATH: ${{ inputs.path }} INPUT_REPOSITORY: ${{ inputs.repository }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} SARIF_FILE: ${{ inputs.sarif-file }} run: | set +e - decided sentry "$INPUT_PATH" \ - --repository "$INPUT_REPOSITORY" \ - $SENTRY_MODE \ - --sarif > "$SARIF_FILE" + if [ "$INPUT_FULL" = "true" ]; then + decided sentry "$INPUT_PATH" \ + --repository "$INPUT_REPOSITORY" \ + --full \ + --sarif > "$SARIF_FILE" + else + BASE="$INPUT_BASE" + if [ -z "$BASE" ]; then + BASE="$PR_BASE_SHA" + fi + if [ -z "$BASE" ]; then + echo "::error::Sentry needs a base revision outside a pull_request event; set the base input or use full=true." + code=2 + echo "exit_code=$code" >> "$GITHUB_OUTPUT" + exit 0 + fi + decided sentry "$INPUT_PATH" \ + --repository "$INPUT_REPOSITORY" \ + --base "$BASE" \ + --sarif > "$SARIF_FILE" + fi code=$? echo "exit_code=$code" >> "$GITHUB_OUTPUT" diff --git a/tests/test_facade_packaging.py b/tests/test_facade_packaging.py index 9ab116b..30ae03a 100644 --- a/tests/test_facade_packaging.py +++ b/tests/test_facade_packaging.py @@ -41,4 +41,9 @@ def test_packager_emits_root_actions_with_facade_relative_installer(tmp_path): assert "../../shared/install-native.sh" not in action assert (output / name / "shared" / "install-native.sh").is_file() assert "pip install" not in action + sentry = output / "sentry" + assert "Deterministic code enforcement" in ( + sentry / "README.md" + ).read_text(encoding="utf-8") + assert 'default: "0.26.0"' in (sentry / "action.yml").read_text(encoding="utf-8") assert not (output / "recordkeeper" / "action.yml").exists() diff --git a/tests/test_sentry_action.py b/tests/test_sentry_action.py index 68965d5..e306c81 100644 --- a/tests/test_sentry_action.py +++ b/tests/test_sentry_action.py @@ -24,9 +24,12 @@ def test_sentry_supports_diff_and_full_tree_modes(): inputs = action()["inputs"] assert inputs["base"]["default"] == "" assert inputs["full"]["default"] == "false" + assert inputs["asdecided-version"]["default"] == "0.26.0" commands = " ".join(step.get("run", "") for step in action()["runs"]["steps"]) assert "--full" in commands - assert "--base" in commands + assert '--base "$BASE"' in commands + assert "$SENTRY_MODE" not in commands + assert "$GITHUB_ENV" not in commands def test_sentry_uploads_one_sarif_and_resurfaces_exit():