generated from mathias/template-go-web
feat(oath): gate cad-atlas's own real candidate, not swedsl's toy stub (#8)
oathcandidate/ is a separate Go module (mirrors swedsl's own oath/testdata/selfcandidate pattern, keeping var-go's transitive deps out of the deployed atlas binary) whose Build() parses the committed .gitea/workflows/cd.yml and checks the "oath" job exists and invokes cmd/vargo-gate. TDD: passes against the real file, fails closed on a fixture missing the job. Rewires the oath CI job to go-run vargo-gate from its real module path (git.d-ma.be/mathias/swedsl/oath/cmd/vargo-gate@oath/v0.28.0, unblocked by swedsl#35/#38) against VARGO_CANDIDATE_DIR=oathcandidate, instead of checking out swedsl and gating its hardcoded toy fixture. Private-module auth via a short-lived GIT_ASKPASS script (token never in argv, never written to git config, matches act_runner's env:-block-with-secrets gotcha). Discovered along the way: var-go's parser needs single-line, period-separated oath sentences with no Given/When/Then/And keyword stripping — this repo's older oaths (incl. #1) used an unverified multi-line keyword-prefixed style. #8's oath uses the proven format. Still not required by branch protection pending a real-PR confirmation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+8
-10
@@ -54,16 +54,14 @@ assessor-loop ledger) → `06 PR → CI` (go test/vet/lint/govulncheck + **var-g
|
|||||||
|
|
||||||
This repo is built *through* the workflow it depicts. It is `dispatch-allow`-enabled, and its
|
This repo is built *through* the workflow it depicts. It is `dispatch-allow`-enabled, and its
|
||||||
own build increments are governed by a **var-go Oath** embedded in their spec issues (see the
|
own build increments are governed by a **var-go Oath** embedded in their spec issues (see the
|
||||||
Stage-03 tracking issue). Bootstrapping honesty (per swedsl honest-stub discipline): `cmd/vargo-gate`
|
Stage-03 tracking issue). `cmd/vargo-gate` is wired into `.gitea/workflows/cd.yml`'s `oath` job —
|
||||||
is wired into `.gitea/workflows/cd.yml`'s `oath` job (issue #1, verified with a real PR) — it runs
|
on every pull_request it fetches the linked issue's oath and gates cad-atlas's **own real
|
||||||
on every pull_request, fetches the linked issue's oath, and posts a `var-go/oath` commit status.
|
candidate** (`oathcandidate/`, #8): it parses the committed `.gitea/workflows/cd.yml` and checks
|
||||||
But its candidate is still swedsl's hardcoded toy self-test registry, not cad-atlas's own — it
|
the `oath` job actually exists and invokes `cmd/vargo-gate`, then posts the `var-go/oath` commit
|
||||||
fails closed against any real oath. var-go's Executor/Reviewer path is **not** the blocker (that
|
status. This is a real check (TDD'd: passes on the real file, fails closed on a fixture missing
|
||||||
was explicitly killed by swedsl's ADR-0002, swedsl#27 — var-go is gate-only by design, each
|
the job), not swedsl's toy self-test stub — swedsl#35 (import path) and swedsl#38 (real-candidate
|
||||||
consumer supplies its own candidate). The real blocker is `swedsl/oath`'s import path
|
subprocess gating) unblocked it. Still **not** required by branch protection until proven green on
|
||||||
(`swedsl#35`); once fixed, cad-atlas writes its own candidate (`#8`). The status is **not**
|
a real PR (#8). Disclosed in the CI config comment, this doc, and `docs/INCEPTION-OATH.md`.
|
||||||
required by branch protection, so it can't block merges yet. Disclosed in the CI config comment,
|
|
||||||
this doc, and `docs/INCEPTION-OATH.md`.
|
|
||||||
|
|
||||||
## Brain references (source of truth — `brain_get <path>`)
|
## Brain references (source of truth — `brain_get <path>`)
|
||||||
|
|
||||||
|
|||||||
+48
-25
@@ -53,50 +53,73 @@ jobs:
|
|||||||
- name: Run checks
|
- name: Run checks
|
||||||
run: task check
|
run: task check
|
||||||
|
|
||||||
|
- name: oathcandidate module — vet + test (private dep, short-lived askpass)
|
||||||
|
working-directory: oathcandidate
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
export DMABE_GITEA_API_TOKEN='${{ secrets.DMABE_GITEA_API_TOKEN }}'
|
||||||
|
ASKPASS=$(mktemp)
|
||||||
|
{ echo '#!/bin/sh'
|
||||||
|
echo 'case "$1" in'
|
||||||
|
echo ' *Username*) echo oauth2 ;;'
|
||||||
|
echo ' *) echo "$DMABE_GITEA_API_TOKEN" ;;'
|
||||||
|
echo 'esac'
|
||||||
|
} > "$ASKPASS"
|
||||||
|
chmod 700 "$ASKPASS"
|
||||||
|
export GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 GOPRIVATE=git.d-ma.be
|
||||||
|
go vet ./...
|
||||||
|
go test ./...
|
||||||
|
rm -f "$ASKPASS"
|
||||||
|
|
||||||
oath:
|
oath:
|
||||||
name: var-go/oath
|
name: var-go/oath
|
||||||
needs: guard
|
needs: guard
|
||||||
# Only a real pull_request event carries a linked-issue oath to gate (mirrors
|
# cad-atlas's own real candidate (#8): oathcandidate/ parses the committed
|
||||||
# swedsl's own oath job, .gitea/workflows/ci.yml). v1 simplification (swedsl#30):
|
# .gitea/workflows/cd.yml and gates it against cad-atlas#8's oath — replacing the
|
||||||
# the oath issue number is the PR's OWN number.
|
# earlier wiring-only proof (#1) that always gated swedsl's toy self-test fixture
|
||||||
#
|
# and always failed closed. cmd/vargo-gate (swedsl#35/#37/#38) now go-installs
|
||||||
# DISCLOSED LIMITATION (honest-stub discipline, see docs/INCEPTION-OATH.md S3 and
|
# cleanly from its real module path and runs the candidate module in a sandboxed
|
||||||
# knowledge/swedsl-vargo-sprint1-enforcement-teeth-verdict.md): cmd/vargo-gate's
|
# subprocess (SubprocessGate, ADR-0003) — a green status here means "the committed
|
||||||
# candidate is a hardcoded toy self-test registry (swedsl's own #9 fixture
|
# CI config satisfies its oath", not merely "the wiring ran". Still NOT required by
|
||||||
# vocabulary), not a real PR-diff checker. It will fail closed against any oath
|
# branch protection (#8) until proven green on a real PR.
|
||||||
# that isn't that toy vocabulary — which is every real oath, including this repo's
|
|
||||||
# own #1. A red or green "var-go/oath" status here currently proves the WIRING
|
|
||||||
# (fetch issue -> gate -> post commit status) runs end-to-end on a real PR, not
|
|
||||||
# that the PR satisfies its linked issue's oath. Deliberately NOT required by
|
|
||||||
# branch protection until cad-atlas has its own candidate matching its real oath
|
|
||||||
# vocabulary (#8, blocked on swedsl/oath's import path, swedsl#35) — making it
|
|
||||||
# required now would permanently block every cad-atlas PR.
|
|
||||||
if: needs.guard.outputs.is_template != 'true' && github.event_name == 'pull_request'
|
if: needs.guard.outputs.is_template != 'true' && github.event_name == 'pull_request'
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout swedsl (var-go source — not go-installable, module path isn't a real import path)
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: mathias/swedsl
|
|
||||||
path: swedsl
|
|
||||||
token: ${{ secrets.DMABE_GITEA_API_TOKEN }}
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: swedsl/oath/go.mod
|
go-version-file: oathcandidate/go.mod
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: Run vargo-gate (fetch -> gate -> post status against this PR)
|
- name: Run vargo-gate (fetch linked oath -> sandboxed-gate the real candidate -> post status)
|
||||||
working-directory: swedsl/oath
|
|
||||||
env:
|
env:
|
||||||
VARGO_GITEA_BASEURL: ${{ github.server_url }}
|
VARGO_GITEA_BASEURL: ${{ github.server_url }}
|
||||||
VARGO_GITEA_OWNER: ${{ github.repository_owner }}
|
VARGO_GITEA_OWNER: ${{ github.repository_owner }}
|
||||||
VARGO_GITEA_REPO: cad-atlas
|
VARGO_GITEA_REPO: cad-atlas
|
||||||
|
# Oath issue resolution (swedsl#38): a "Closes #NN" reference in the PR body
|
||||||
|
# picks the linked oath issue; VARGO_GITEA_ISSUE is the fallback (PR's own
|
||||||
|
# number, correct only for a PR filed directly against its oath issue).
|
||||||
|
VARGO_PR_BODY: ${{ github.event.pull_request.body }}
|
||||||
VARGO_GITEA_ISSUE: ${{ github.event.pull_request.number }}
|
VARGO_GITEA_ISSUE: ${{ github.event.pull_request.number }}
|
||||||
VARGO_GITEA_SHA: ${{ github.event.pull_request.head.sha }}
|
VARGO_GITEA_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
VARGO_CANDIDATE_DIR: oathcandidate
|
||||||
|
# Sandbox is ON by default (untrusted PR code runs in a fresh user+net
|
||||||
|
# namespace, swedsl#37); no need to set VARGO_SANDBOX here.
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
export DMABE_GITEA_API_TOKEN='${{ secrets.DMABE_GITEA_API_TOKEN }}'
|
export DMABE_GITEA_API_TOKEN='${{ secrets.DMABE_GITEA_API_TOKEN }}'
|
||||||
go run ./cmd/vargo-gate
|
ASKPASS=$(mktemp)
|
||||||
|
{ echo '#!/bin/sh'
|
||||||
|
echo 'case "$1" in'
|
||||||
|
echo ' *Username*) echo oauth2 ;;'
|
||||||
|
echo ' *) echo "$DMABE_GITEA_API_TOKEN" ;;'
|
||||||
|
echo 'esac'
|
||||||
|
} > "$ASKPASS"
|
||||||
|
chmod 700 "$ASKPASS"
|
||||||
|
export GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 GOPRIVATE=git.d-ma.be
|
||||||
|
go run git.d-ma.be/mathias/swedsl/oath/cmd/vargo-gate@oath/v0.28.0
|
||||||
|
rm -f "$ASKPASS"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build & Import
|
name: Build & Import
|
||||||
|
|||||||
+11
-6
@@ -4,8 +4,9 @@ The acceptance contract for standing up cad-atlas. The sprint is finalized only
|
|||||||
Oath holds. Methodology: brain `wiki/homelab/decisions/inception-sprint-and-oath.md`.
|
Oath holds. Methodology: brain `wiki/homelab/decisions/inception-sprint-and-oath.md`.
|
||||||
|
|
||||||
> **Status of enforcement:** this Oath is currently **advisory** (human-verified). `var-go/oath`
|
> **Status of enforcement:** this Oath is currently **advisory** (human-verified). `var-go/oath`
|
||||||
> is wired (issue #1) and runs on every PR, but its candidate is a toy self-test — it fails closed
|
> gates a real candidate now (`oathcandidate/`, #8 — parses the committed CI workflow, TDD'd
|
||||||
> against any real oath and is not required by branch protection. See the honesty rule below.
|
> pass/fail-closed) but isn't yet required by branch protection pending a real-PR confirmation.
|
||||||
|
> See the honesty rule below.
|
||||||
|
|
||||||
## General clauses (any inception sprint)
|
## General clauses (any inception sprint)
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ Oath holds. Methodology: brain `wiki/homelab/decisions/inception-sprint-and-oath
|
|||||||
|---|--------|--------|----------|
|
|---|--------|--------|----------|
|
||||||
| S1 | Atlas served at `/`, renders all 9 stages signal→pod | ✅ | `internal/web/handler.go` + `static/cad-atlas.html` |
|
| S1 | Atlas served at `/`, renders all 9 stages signal→pod | ✅ | `internal/web/handler.go` + `static/cad-atlas.html` |
|
||||||
| S2 | Oath covered in the viz (stages 03 + 06) | ✅ | var-go Oath nodes in the atlas |
|
| S2 | Oath covered in the viz (stages 03 + 06) | ✅ | var-go Oath nodes in the atlas |
|
||||||
| S3 | `var-go/oath` enforces cad-atlas's own PRs | ⏸ **wired, not enforcing → #8** | `oath` job runs + posts status (#1, verified with a real PR). Not branch-protection-required: candidate is still swedsl's toy self-test, fails closed on every real oath. Blocked on `swedsl/oath` import path (swedsl#35) → cad-atlas writing its own candidate (#8). Not blocked on an Executor — that path was killed by swedsl's ADR-0002. See honesty rule. |
|
| S3 | `var-go/oath` enforces cad-atlas's own PRs | ⏸ **real candidate wired, not enforcing → #8** | `oathcandidate/` gates the real `.gitea/workflows/cd.yml` (TDD green: passes real file, fails closed on a fixture missing the job) via swedsl's sandboxed `SubprocessGate` (swedsl#35/#38). Not yet branch-protection-required — awaiting confirmation on a real PR. See honesty rule. |
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
@@ -38,9 +39,13 @@ namespace `cad-atlas`, 1 replica, `cad-atlas:80 → :8080` (manifests in `mathia
|
|||||||
|
|
||||||
A clause blocked by an external dependency is **descoped and tracked, never marked satisfied** —
|
A clause blocked by an external dependency is **descoped and tracked, never marked satisfied** —
|
||||||
a self-lying Oath is a rubber stamp, the exact failure the Oath exists to prevent. S3's real
|
a self-lying Oath is a rubber stamp, the exact failure the Oath exists to prevent. S3's real
|
||||||
enforcement depends on `swedsl/oath` becoming importable (swedsl#35) and cad-atlas writing its own
|
candidate is wired (#8) but branch-protection enforcement waits on a real-PR confirmation, tracked
|
||||||
candidate (#8); it is tracked there, not claimed here. The `DMABE_GITEA_API_TOKEN` Actions secret
|
there, not claimed here. The `DMABE_GITEA_API_TOKEN` Actions secret is pre-provisioned so #1 and
|
||||||
is pre-provisioned so #1 landed without a secret-write.
|
#8 both landed without a secret-write.
|
||||||
|
|
||||||
|
Also surfaced by #8: this file's own "Oath (advisory form)" below predates the discovery that
|
||||||
|
var-go's parser requires single-line, period-separated sentences with no `Given`/`Then`/`And`
|
||||||
|
keyword stripping — it has never been machine-gated and would need reformatting first if it ever is.
|
||||||
|
|
||||||
## The Oath (advisory form)
|
## The Oath (advisory form)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
// Package oathcandidate supplies cad-atlas's own real var-go candidate (cad-atlas#8):
|
||||||
|
// steps that gate its own CI-workflow oath by actually parsing the committed
|
||||||
|
// .gitea/workflows/cd.yml, not a stub that hardcodes an unrelated toy vocabulary.
|
||||||
|
// var-go injects and owns the gate across the subprocess boundary (SubprocessGate,
|
||||||
|
// ADR-0003), so this package supplies only the prose->behaviour binding and never a
|
||||||
|
// verdict — it cannot self-certify.
|
||||||
|
package oathcandidate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
oath "git.d-ma.be/mathias/swedsl/oath"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// workflowState is the candidate's domain: the job names and concatenated step-run
|
||||||
|
// scripts parsed out of one Gitea Actions workflow file.
|
||||||
|
type workflowState struct {
|
||||||
|
jobNames map[string]bool
|
||||||
|
jobRuns map[string]string // job name -> every step's `run:` script, concatenated
|
||||||
|
}
|
||||||
|
|
||||||
|
type workflowFile struct {
|
||||||
|
Jobs map[string]struct {
|
||||||
|
Steps []struct {
|
||||||
|
Run string `yaml:"run"`
|
||||||
|
} `yaml:"steps"`
|
||||||
|
} `yaml:"jobs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build returns cad-atlas's candidate registry. cmd/vargo-gate runs the generated
|
||||||
|
// harness with cwd = this module's own directory (SubprocessGate's
|
||||||
|
// cmd.Dir = candidateModuleDir contract) — one level under the cad-atlas repo root
|
||||||
|
// in cad-atlas's real layout — so a workflow path in the oath text like
|
||||||
|
// ".gitea/workflows/cd.yml" is read relative to "..".
|
||||||
|
func Build() *oath.Registry[workflowState] {
|
||||||
|
reg := oath.NewRegistry[workflowState]()
|
||||||
|
|
||||||
|
if err := reg.Stimulus(`the CI workflow file {string} is parsed`,
|
||||||
|
func(_ workflowState, path string) workflowState {
|
||||||
|
return parseWorkflow(path)
|
||||||
|
}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := reg.Sensor(`it defines a job named {string}`,
|
||||||
|
func(s workflowState, name string) string {
|
||||||
|
if s.jobNames[name] {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return "<no such job>"
|
||||||
|
}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks what the workflow file can actually attest to: the job's run script
|
||||||
|
// invokes the gate binary. The "var-go/oath" commit-status context string
|
||||||
|
// itself lives in vargo-gate's Go code, not the YAML — not something this
|
||||||
|
// file-level check can see, so it isn't what's asserted here.
|
||||||
|
if err := reg.Sensor(`the job named {string} invokes {string}`,
|
||||||
|
func(s workflowState, job, cmd string) (string, string) {
|
||||||
|
run, ok := s.jobRuns[job]
|
||||||
|
foundJob := "<no such job>"
|
||||||
|
if ok {
|
||||||
|
foundJob = job
|
||||||
|
}
|
||||||
|
foundCmd := cmd
|
||||||
|
if !ok || !strings.Contains(run, cmd) {
|
||||||
|
foundCmd = "<not invoked>"
|
||||||
|
}
|
||||||
|
return foundJob, foundCmd
|
||||||
|
}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reg
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseWorkflow reads and parses a Gitea Actions workflow file relative to the
|
||||||
|
// repo root (see Build's doc comment for the cwd contract). A read or parse
|
||||||
|
// failure returns an empty state — every sensor then observes "not found",
|
||||||
|
// which fails the gate closed rather than silently skipping the check.
|
||||||
|
func parseWorkflow(repoRelativePath string) workflowState {
|
||||||
|
state := workflowState{jobNames: map[string]bool{}, jobRuns: map[string]string{}}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(filepath.Join("..", repoRelativePath))
|
||||||
|
if err != nil {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
var wf workflowFile
|
||||||
|
if err := yaml.Unmarshal(data, &wf); err != nil {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, job := range wf.Jobs {
|
||||||
|
state.jobNames[name] = true
|
||||||
|
var runs strings.Builder
|
||||||
|
for _, step := range job.Steps {
|
||||||
|
runs.WriteString(step.Run)
|
||||||
|
runs.WriteString("\n")
|
||||||
|
}
|
||||||
|
state.jobRuns[name] = runs.String()
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package oathcandidate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
oath "git.d-ma.be/mathias/swedsl/oath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// realOath is cad-atlas#8's actual oath text — the same var block committed to
|
||||||
|
// that issue. Gating it against the REAL checked-out .gitea/workflows/cd.yml
|
||||||
|
// proves the candidate reads real CI config, not a fixture standing in for it.
|
||||||
|
//
|
||||||
|
// Format note (discovered writing this test): var-go's parser requires a
|
||||||
|
// SINGLE-LINE paragraph — sentences are split by "." within that line, not by
|
||||||
|
// newline — and does NOT strip Given/When/Then/And keywords before matching a
|
||||||
|
// step. cad-atlas's older oaths (e.g. issue #1) use a multi-line, keyword-prefixed
|
||||||
|
// style that was never actually exercised against this parser (every prior gate
|
||||||
|
// run errored before reaching real sentence matching). Plain declarative
|
||||||
|
// sentences, period-separated, one line — see swedsl's own gate_test.go fixtures.
|
||||||
|
const realOath = "```var\n" +
|
||||||
|
`the CI workflow file ".gitea/workflows/cd.yml" is parsed. it defines a job named "oath". the job named "oath" invokes "cmd/vargo-gate".` +
|
||||||
|
"\n```\n"
|
||||||
|
|
||||||
|
// TestBuild_GatesRealWorkflow is named before Build existed (TDD): it fails to
|
||||||
|
// compile until Build() and the workflow-parsing steps exist, and fails to pass
|
||||||
|
// until they parse the REAL committed cd.yml correctly — this is the file that
|
||||||
|
// must go from red to green, not a mock.
|
||||||
|
func TestBuild_GatesRealWorkflow(t *testing.T) {
|
||||||
|
// go test's cwd is already this package's dir (oathcandidate/), matching
|
||||||
|
// SubprocessGate's cmd.Dir = candidateModuleDir contract exactly — no chdir
|
||||||
|
// needed to reproduce it here.
|
||||||
|
verdict, err := oath.Gate([]byte(realOath), Build())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gate returned error: %v", err)
|
||||||
|
}
|
||||||
|
if !verdict.Pass {
|
||||||
|
if verdict.Failure != nil {
|
||||||
|
t.Fatalf("Gate did not pass: failure=%+v", *verdict.Failure)
|
||||||
|
}
|
||||||
|
t.Fatalf("Gate did not pass against the real committed cd.yml: %+v", verdict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBuild_FailsClosedOnMissingJob proves the candidate is a REAL check, not a
|
||||||
|
// rubber stamp: gating a workflow file that has no "oath" job must fail.
|
||||||
|
func TestBuild_FailsClosedOnMissingJob(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
workflowsDir := filepath.Join(dir, ".gitea", "workflows")
|
||||||
|
if err := os.MkdirAll(workflowsDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
noOathJob := "jobs:\n check:\n steps:\n - run: go test ./...\n"
|
||||||
|
if err := os.WriteFile(filepath.Join(workflowsDir, "cd.yml"), []byte(noOathJob), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// SubprocessGate always runs the candidate with cmd.Dir = candidateModuleDir,
|
||||||
|
// one level under the repo root (cad-atlas's real layout) — reproduce that by
|
||||||
|
// chdir-ing into a sibling "candidate/" dir under the fixture root.
|
||||||
|
candDir := filepath.Join(dir, "candidate")
|
||||||
|
if err := os.MkdirAll(candDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Chdir(candDir); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = os.Chdir(cwd) })
|
||||||
|
|
||||||
|
verdict, err := oath.Gate([]byte(realOath), Build())
|
||||||
|
if err == nil && verdict.Pass {
|
||||||
|
t.Fatalf("expected the gate to fail closed on a workflow with no oath job, got Pass=true")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Package oathcandidate is cad-atlas's committed real candidate: the STEPS that
|
||||||
|
// gate its own CI-workflow oath (cad-atlas#8). Deliberately a separate module (not
|
||||||
|
// part of the main cad-atlas module) so var-go's transitive deps (cucumber-expressions,
|
||||||
|
// goldmark) never link into the deployed atlas binary — mirrors swedsl's own
|
||||||
|
// oath/testdata/selfcandidate pattern.
|
||||||
|
module oathcandidate
|
||||||
|
|
||||||
|
go 1.26.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.d-ma.be/mathias/swedsl/oath v0.28.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cucumber/cucumber-expressions/go/v18 v18.1.0 // indirect
|
||||||
|
github.com/yuin/goldmark v1.8.2 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
git.d-ma.be/mathias/swedsl/oath v0.28.0 h1:q4WXlGtMlDymhmuw9Pdc025OTLs1wl8THsrz/raeMxs=
|
||||||
|
git.d-ma.be/mathias/swedsl/oath v0.28.0/go.mod h1:kEOX7Wubf3g/HTKzuoHD4fm6zNSl55cSV/qgy+ezMoI=
|
||||||
|
github.com/cucumber/cucumber-expressions/go/v18 v18.1.0 h1:zvZFnbmtQxwHq6ru5gHxpfBloLq9wmjoKbdwOzt/XNA=
|
||||||
|
github.com/cucumber/cucumber-expressions/go/v18 v18.1.0/go.mod h1:+Qe2kvmilsdGRFJ+zlkjXp84rPEf6O/idcoOsvnIORY=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||||
|
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
Reference in New Issue
Block a user