Pular para conteúdo

AUR-018 — Fixar contratos SCM e CI seguros

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

Behavior

This card fixes, as data, the capability and trust-boundary matrix that any future GitHub, Gitea, or GitLab adapter must respect. It does not implement a client, a webhook receiver, or a CI pipeline. Two roles are distinguished everywhere in the matrix:

  • analyzer — reads a change, including one whose head may be hostile (a fork or an external branch not owned by a maintainer). It is presented only a read-only credential, on every forge, with no exception.
  • publisher — writes a result back (a comment, check, status, or release). It always runs in a context decoupled from the hostile-head checkout the analyzer used, and only there may it hold a write-scoped credential.

Contract

  • .board/research/scm-ci.md is the dated, versioned research: one row per vendor source actually consulted, and a matrix table naming the analyzer and publisher credential, scope, and trust boundary for GitHub, Gitea, and GitLab.
  • standards/scm/capabilities.yaml is the machine-readable form of that matrix: forges.<name>.analyzer, forges.<name>.publisher, and the cross-forge rule.hostile_head_read_only invariant. A future adapter card reads this file; it does not re-derive scopes from prose.
  • tests/acceptance/AUR-018.sh::AC-001 is the sole executable gate for this card: it requires .board/research/scm-ci.md to exist, be non-empty, and contain, case-insensitively, GitHub, Gitea, GitLab, analyzer, publisher, and least privilege. Absence of any one term is the invalid vector and exits non-zero before any effect outside paths.
  • tests/integration/AUR-018_test.go (TestAUR018Boundary) is the structural lint, discoverable by go test ./tests/integration/...: it parses standards/scm/capabilities.yaml, requires all three forges to declare both roles, requires allow_write_on_hostile_head: false everywhere, cross-checks that every forge named in capabilities.yaml also appears in the matrix table of scm-ci.md (so the machine-readable limits and the human-readable research can never diverge), and requires every source cited in scm-ci.md to name an allowlisted vendor-docs domain together with a version or an access date — the same shape MUT-002 targets.

Limits

  • standards/scm/capabilities.yaml's limits.max_token_scopes_per_role: 3 bounds how many scope entries an adapter may request per role per forge.
  • limits.allowed_analyzer_ci_events / forbidden_analyzer_ci_events bound which CI trigger the analyzer role may run under; GitHub's pull_request_target and workflow_run are explicitly forbidden for the analyzer because GitHub's own docs describe them as privileged even when triggered from a public fork.
  • No role in any forge entry may set allow_write_on_hostile_head: true; MUT-001 proves the acceptance path rejects that change.

Example (no secret involved)

$ ./.board/bin/oci-run --profile bootstrap-readonly-v1 --card AUR-018
{"card":"AUR-018","scenario":"AC-001","result":"pass"}
$ echo $?
0

Removing any one of the six required terms from .board/research/scm-ci.md (for example replacing least privilege with a paraphrase) reproduces the invalid vector:

$ sed 's/least privilege/least-priv only/' .board/research/scm-ci.md > /tmp/scm-ci-mut.md
$ f=/tmp/scm-ci-mut.md bash -c 'grep -Fiq "least privilege" "$f"'; echo $?
1

Diagnostic procedure

  1. Run the accept command above and capture exit_code and stdout_sha256 from the oci-run JSON envelope.
  2. If exit_code != 0, stderr names the missing term (AUR-018/AC-001: SCM/CI baseline absent when the file itself is missing or empty, or a bare non-zero exit from the term loop otherwise) — check .board/research/scm-ci.md for the six required terms first.
  3. Run go test ./tests/integration/... to check the structural lint independently of the shell gate; a failure there names which forge, role, or source row is missing or malformed in standards/scm/capabilities.yaml or .board/research/scm-ci.md.
  4. Confirm no output at any step contains AURUM_SECRET_CANARY.