Skip to content

Commit 49cbf06

Browse files
Merge branch 'main' into fix/gbf-legacy-findings-resolve-at-source
2 parents 0e91c10 + 0feeac1 commit 49cbf06

116 files changed

Lines changed: 8471 additions & 5747 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/hypatia-scan.yml

Lines changed: 46 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,25 @@ on:
1010
schedule:
1111
- cron: '0 0 * * 0' # Weekly on Sunday
1212
workflow_dispatch:
13+
# Estate guardrail: cancel superseded runs so re-pushes don't pile up
14+
# queued runs across the estate. Safe here because this workflow only
15+
# performs read-only checks/lint/test/scan with no publish or mutation.
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
1319

1420
permissions:
1521
contents: read
16-
# `pull-requests: write` is needed for the "Comment on PR with findings"
17-
# step to POST a results summary. Note: on Dependabot PRs the token is
18-
# downgraded to read-only regardless, so that step is also marked
19-
# continue-on-error below.
22+
# security-events: read lets the built-in GITHUB_TOKEN query this
23+
# repo's own Dependabot alerts via the Hypatia DependabotAlerts rule
24+
# (DA001-DA004). Without this, `scan_from_path` gets HTTP 403 and
25+
# the rule silently returns no findings.
26+
# See 007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md.
27+
security-events: read
28+
# pull-requests: write lets the advisory "Comment on PR with findings"
29+
# step post its summary. Without it the built-in GITHUB_TOKEN gets
30+
# "Resource not accessible by integration" and (absent continue-on-error)
31+
# hard-fails the scan — exactly what the gate-decoupling design forbids.
2032
pull-requests: write
2133

2234
jobs:
@@ -26,7 +38,7 @@ jobs:
2638

2739
steps:
2840
- name: Checkout repository
29-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
41+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3042
with:
3143
fetch-depth: 0 # Full history for better pattern analysis
3244

@@ -36,17 +48,9 @@ jobs:
3648
elixir-version: '1.19.4'
3749
otp-version: '28.3'
3850

39-
- name: Clone Hypatia (or use checkout when scanning hypatia itself)
40-
env:
41-
REPO: ${{ github.repository }}
51+
- name: Clone Hypatia
4252
run: |
43-
# When scanning hypatia from inside hypatia, point $HOME/hypatia
44-
# at the PR/branch checkout instead of cloning main — otherwise
45-
# CLI changes can never pass their own gate (the scanner binary
46-
# would always come from main and ignore new flags).
47-
if [ "$REPO" = "hyperpolymath/hypatia" ]; then
48-
ln -sfn "${GITHUB_WORKSPACE}" "$HOME/hypatia"
49-
elif [ ! -d "$HOME/hypatia" ]; then
53+
if [ ! -d "$HOME/hypatia" ]; then
5054
git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia"
5155
fi
5256
@@ -62,39 +66,16 @@ jobs:
6266
- name: Run Hypatia scan
6367
id: scan
6468
env:
65-
# Suppress the "Warning: Dependabot alerts unavailable: GITHUB_TOKEN
66-
# not set" line so the run is silent-warning-free. The token is
67-
# read-only by default and only used to query Dependabot alerts.
69+
# Pass the built-in Actions token through to Hypatia so the
70+
# DependabotAlerts rule can query this repo's own alerts.
71+
# For cross-repo scanning (fleet-coordinator scan-supervised),
72+
# a PAT with `security_events` scope is required instead.
6873
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69-
REPO: ${{ github.repository }}
7074
run: |
71-
echo "Scanning repository: $REPO"
72-
73-
# Run scanner with --exit-zero so a findings-found exit-1 does
74-
# NOT short-circuit the rest of this step under `set -e`. The
75-
# downstream "Check for critical or high-severity issues" step
76-
# is the explicit gate. See hyperpolymath/hypatia#213.
77-
#
78-
# Guard against the scanner producing no output (a crash, an
79-
# unknown flag, etc.): if hypatia-findings.json is empty or
80-
# missing after the run, fall back to "[]" so the jq calls
81-
# below don't 9 the whole gate. We surface stderr so the
82-
# underlying scanner failure is still visible in the log.
83-
set +e
84-
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero \
85-
> hypatia-findings.json 2> hypatia-scan.stderr
86-
SCAN_EXIT=$?
87-
set -e
88-
echo "Scanner exit: $SCAN_EXIT"
89-
if [ -s hypatia-scan.stderr ]; then
90-
echo "--- scanner stderr ---"
91-
cat hypatia-scan.stderr
92-
echo "--- end stderr ---"
93-
fi
94-
if ! jq empty hypatia-findings.json 2>/dev/null; then
95-
echo "Scanner did not produce valid JSON; defaulting to empty findings."
96-
echo "[]" > hypatia-findings.json
97-
fi
75+
echo "Scanning repository: ${{ github.repository }}"
76+
77+
# Run scanner (exits non-zero when findings exist — suppress to continue)
78+
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json || true
9879
9980
# Count findings
10081
FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0)
@@ -116,7 +97,7 @@ jobs:
11697
echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY
11798
11899
- name: Upload findings artifact
119-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
100+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
120101
with:
121102
name: hypatia-findings
122103
path: hypatia-findings.json
@@ -191,73 +172,31 @@ jobs:
191172
# Cleanup
192173
rm -rf "$FLEET_DIR"
193174
194-
- name: Check for critical or high-severity issues
195-
if: steps.scan.outputs.critical > 0 || steps.scan.outputs.high > 0
196-
env:
197-
CRITICAL_COUNT: ${{ steps.scan.outputs.critical }}
198-
HIGH_COUNT: ${{ steps.scan.outputs.high }}
175+
- name: Check for critical issues
176+
if: steps.scan.outputs.critical > 0
199177
run: |
200-
echo "Total critical/high: $CRITICAL_COUNT critical, $HIGH_COUNT high"
201-
202-
# Baseline-aware gate: pre-existing accepted findings live in
203-
# .hypatia-baseline.json (committed). New critical/high findings
204-
# not in the baseline still fail the build. Findings are matched
205-
# on (severity, rule_module, type, file) tuple with absolute
206-
# build paths normalised to repo-relative.
207-
if [ -f .hypatia-baseline.json ]; then
208-
# Normalise + project just the comparable keys from the current scan.
209-
jq '[ .[] | select(.severity == "critical" or .severity == "high")
210-
| {severity, rule_module, type,
211-
file: (.file | sub("^/home/runner/work/[^/]+/[^/]+/"; "")
212-
| sub("^/github/workspace/"; "")),
213-
action} ]' hypatia-findings.json > findings-current.json
214-
215-
# Subtract baseline. A current finding is "new" iff there's no
216-
# element in baseline equal to it (by-value).
217-
jq --slurpfile base .hypatia-baseline.json \
218-
'. as $cur | $cur | map(. as $f | select(($base[0] | any(. == $f)) | not))' \
219-
findings-current.json > findings-new.json
220-
new_count=$(jq 'length' findings-new.json)
221-
222-
if [ "$new_count" -gt 0 ]; then
223-
echo "::error::$new_count new critical/high finding(s) outside the baseline:"
224-
jq -r '.[] | " [\(.severity)] \(.rule_module)/\(.type) — \(.file)"' findings-new.json
225-
echo
226-
echo "If these are intentional, regenerate .hypatia-baseline.json:"
227-
echo " jq '[.[] | select(.severity == \"critical\" or .severity == \"high\") | {severity, rule_module, type, file, action}] | sort_by(.severity, .rule_module, .type, .file)' hypatia-findings.json > .hypatia-baseline.json"
228-
exit 1
229-
fi
230-
echo "All critical/high findings present in baseline — gate passes."
231-
else
232-
echo "No .hypatia-baseline.json — failing on any critical/high (legacy behaviour)."
233-
echo "Review hypatia-findings.json for details"
234-
exit 1
235-
fi
178+
echo "⚠️ Critical security issues found!"
179+
echo "Review hypatia-findings.json for details"
180+
# Don't fail the build yet - just warn
181+
# exit 1
236182
237183
- name: Generate scan report
238-
env:
239-
REPO: ${{ github.repository }}
240-
SHA: ${{ github.sha }}
241-
CRITICAL_COUNT: ${{ steps.scan.outputs.critical }}
242-
HIGH_COUNT: ${{ steps.scan.outputs.high }}
243-
MEDIUM_COUNT: ${{ steps.scan.outputs.medium }}
244-
TOTAL_COUNT: ${{ steps.scan.outputs.findings_count }}
245184
run: |
246185
cat << EOF > hypatia-report.md
247186
# Hypatia Security Scan Report
248187
249-
**Repository:** $REPO
188+
**Repository:** ${{ github.repository }}
250189
**Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
251-
**Commit:** $SHA
190+
**Commit:** ${{ github.sha }}
252191
253192
## Summary
254193
255194
| Severity | Count |
256195
|----------|-------|
257-
| Critical | $CRITICAL_COUNT |
258-
| High | $HIGH_COUNT |
259-
| Medium | $MEDIUM_COUNT |
260-
| **Total**| $TOTAL_COUNT |
196+
| Critical | ${{ steps.scan.outputs.critical }} |
197+
| High | ${{ steps.scan.outputs.high }} |
198+
| Medium | ${{ steps.scan.outputs.medium }} |
199+
| **Total**| ${{ steps.scan.outputs.findings_count }} |
261200
262201
## Next Steps
263202
@@ -276,14 +215,13 @@ jobs:
276215
cat hypatia-report.md >> $GITHUB_STEP_SUMMARY
277216
278217
- name: Comment on PR with findings
279-
# Dependabot PRs always run with a read-only token regardless of the
280-
# workflow's declared permissions, so the createComment call below
281-
# would 403 on every dep-bump PR. The PR comment is informational
282-
# (the check result is already visible in the PR UI); we don't want
283-
# its absence to block merge.
284218
if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0
219+
# Advisory only — posting findings as a PR comment must never gate
220+
# the scan (hypatia#213 gate decoupling). Belt-and-braces alongside
221+
# the pull-requests: write permission above: a token/API hiccup or
222+
# a fork PR (read-only token) skips the comment, not the check.
285223
continue-on-error: true
286-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v7
224+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7
287225
with:
288226
script: |
289227
const fs = require('fs');
@@ -313,4 +251,4 @@ jobs:
313251
repo: context.repo.repo,
314252
issue_number: context.issue.number,
315253
body: comment
316-
});
254+
});

.github/workflows/rsr-antipattern.yml

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -138,83 +138,6 @@ jobs:
138138
print(f"✅ No TypeScript files outside allowlist ({len(exemption_patterns)} per-repo exemption(s) parsed).")
139139
PYEOF
140140
141-
# Universal builtin allowlist — bridges that need no per-repo declaration.
142-
# Files matching any of these patterns are always allowed.
143-
BUILTIN_GLOBS = [
144-
'*.d.ts',
145-
'**/bindings/**',
146-
'**/tests/**', '**/test/**',
147-
'**/scripts/**',
148-
'**/mcp-adapter/**',
149-
'**/*vscode*/**',
150-
'**/cli/**',
151-
'**/mod.ts',
152-
'**/lsp-server.ts', '**/lsp_server.ts', '**/lsp.ts', '**/*-lsp.ts',
153-
'**/deno-*/**',
154-
'**/node_modules/**',
155-
'**/vendor/**',
156-
'**/examples/**',
157-
'**/ffi/**',
158-
]
159-
160-
# Per-repo exemptions parsed from .claude/CLAUDE.md "TypeScript Exemptions" table.
161-
# Single source of truth — adding a row here unblocks CI for that path.
162-
# Format expected:
163-
# ### TypeScript Exemptions ...
164-
# | Path | Files | Rationale | Unblock condition |
165-
# |---|---|---|---|
166-
# | `path/to/file.ts` | 1 | ... | ... |
167-
# | `dir/*.ts` | 6 | ... | ... |
168-
exemptions = []
169-
claude_md = pathlib.Path('.claude/CLAUDE.md')
170-
if claude_md.exists():
171-
in_table = False
172-
for line in claude_md.read_text(encoding='utf-8').splitlines():
173-
if re.search(r'TypeScript [Ee]xemptions', line):
174-
in_table = True
175-
continue
176-
if in_table and line.startswith(('### ', '## ', '# ')):
177-
break
178-
if in_table and line.startswith('|'):
179-
m = re.match(r'\|\s*`([^`]+)`', line)
180-
if m:
181-
exemptions.append(m.group(1))
182-
183-
# Find all .ts and .tsx files
184-
found = []
185-
for ext in ('ts', 'tsx'):
186-
found.extend(str(p) for p in pathlib.Path('.').rglob(f'*.{ext}'))
187-
188-
def allowed(path):
189-
p = path.lstrip('./')
190-
for g in BUILTIN_GLOBS + exemptions:
191-
if fnmatch.fnmatchcase(p, g):
192-
return True
193-
# also treat glob ending with / as a directory prefix
194-
base = g.rstrip('/').rstrip('*').rstrip('/')
195-
if base and (p == base or p.startswith(base + '/')):
196-
return True
197-
return False
198-
199-
bad = sorted(f for f in found if not allowed(f))
200-
if bad:
201-
print("❌ TypeScript files detected outside the allowlist.\n")
202-
for f in bad:
203-
print(f" {f}")
204-
print()
205-
print("To resolve, either:")
206-
print(" (a) migrate the file to AffineScript")
207-
print(" (see Human_Programming_Guide.adoc migration chapter), OR")
208-
print(" (b) move it to an allowlisted bridge path")
209-
print(" (bindings/, tests/, scripts/, mcp-adapter/, *vscode*/, cli/, deno-*/, etc.), OR")
210-
print(" (c) add an entry to the 'TypeScript Exemptions' table in .claude/CLAUDE.md")
211-
print(" with rationale + unblock condition.")
212-
if exemptions:
213-
print(f"\n(Currently {len(exemptions)} exemption(s) parsed from .claude/CLAUDE.md.)")
214-
sys.exit(1)
215-
print(f"✅ No TypeScript files outside allowlist ({len(exemptions)} per-repo exemption(s) parsed).")
216-
PYEOF
217-
218141
- name: Check for Go
219142
run: |
220143
if find . -name "*.go" | grep -q .; then

.github/workflows/secret-scanner.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ jobs:
2121
- name: TruffleHog Secret Scan
2222
uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3
2323
with:
24-
extra_args: --only-verified --fail
24+
# The action already passes --fail itself; repeating it errors
25+
# ("flag 'fail' cannot be repeated"). Only pass --only-verified.
26+
extra_args: --only-verified
2527

2628
gitleaks:
2729
runs-on: ubuntu-latest

.machine_readable/6a2/AGENTIC.a2ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
#
44
# AGENTIC.a2ml — AI agent constraints and capabilities
5+
name = "agentic"
6+
57
[metadata]
68
version = "0.1.0"
79
last-updated = "2026-04-11"

.machine_readable/6a2/META.a2ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
#
44
# META.a2ml — Gitbot Fleet meta-level information
5+
name = "meta"
6+
57
[metadata]
68
version = "1.0"
79
last-updated = "2026-04-11"

.machine_readable/6a2/NEUROSYM.a2ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
#
44
# NEUROSYM.a2ml — Neurosymbolic integration metadata
5+
name = "neurosym"
6+
57
[metadata]
68
version = "0.1.0"
79
last-updated = "2026-04-11"

.machine_readable/6a2/PLAYBOOK.a2ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
#
44
# PLAYBOOK.a2ml — Operational playbook
5+
name = "playbook"
6+
57
[metadata]
68
version = "0.1.0"
79
last-updated = "2026-04-11"

.machine_readable/CICD-PATTERNS.a2ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
;; Generated: 2026-01-05 from cicd-hyper-a audit session
66
;; Updated: 2026-01-09 with RSR policy contradiction fixes
77

8+
;; A2ML identity (TOML-shaped manifest header for the a2ml validator)
9+
project = "gitbot-fleet"
10+
name = "gitbot-fleet-cicd-patterns"
11+
version = "1.0"
12+
813
(cicd-patterns
914
(version "1.0")
1015
(project "gitbot-fleet")

.machine_readable/CLADE.a2ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# See: https://github.com/hyperpolymath/gv-clade-index
44

55
[identity]
6+
name = "gitbot-fleet"
67
uuid = "f8cdb015-c230-50f2-b09c-f9e8d37646dd"
78
primary-forge = "github"
89
primary-owner = "hyperpolymath"

.machine_readable/agent_instructions/coverage.a2ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# Reference: ADR-002 in standards/agentic-a2ml/docs/
99

1010
[metadata]
11+
name = "gitbot-fleet-coverage"
1112
version = "1.0.0"
1213
last-updated = "2026-03-24"
1314

0 commit comments

Comments
 (0)