@bookedsolid/rea 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/append.js +12 -1
- package/dist/cache/review-cache.d.ts +115 -0
- package/dist/cache/review-cache.js +200 -0
- package/dist/cli/cache.d.ts +52 -0
- package/dist/cli/cache.js +112 -0
- package/dist/cli/doctor.d.ts +27 -0
- package/dist/cli/doctor.js +85 -3
- package/dist/cli/index.js +41 -0
- package/dist/cli/init.js +16 -0
- package/dist/cli/install/gitignore.d.ts +114 -0
- package/dist/cli/install/gitignore.js +356 -0
- package/dist/cli/upgrade.js +20 -0
- package/dist/gateway/downstream-pool.d.ts +34 -0
- package/dist/gateway/downstream-pool.js +37 -0
- package/dist/gateway/downstream.d.ts +11 -0
- package/dist/gateway/downstream.js +36 -5
- package/dist/gateway/meta/health.d.ts +117 -0
- package/dist/gateway/meta/health.js +108 -0
- package/dist/gateway/server.js +109 -12
- package/dist/policy/loader.d.ts +10 -0
- package/dist/policy/loader.js +2 -0
- package/dist/policy/types.d.ts +20 -0
- package/hooks/push-review-gate.sh +185 -1
- package/package.json +1 -1
|
@@ -56,8 +56,31 @@ fi
|
|
|
56
56
|
# ── 4. Parse command ──────────────────────────────────────────────────────────
|
|
57
57
|
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
58
58
|
|
|
59
|
+
# ── 4a. BUG-008: self-detect git's native pre-push contract ───────────────────
|
|
60
|
+
# When the hook is wired into `.husky/pre-push`, git invokes it with
|
|
61
|
+
# `$1 = remote name`, `$2 = remote url`
|
|
62
|
+
# and delivers one line per refspec on stdin:
|
|
63
|
+
# `<local_ref> <local_sha> <remote_ref> <remote_sha>`
|
|
64
|
+
# The Claude Code PreToolUse wrapper instead delivers JSON on stdin, which is
|
|
65
|
+
# what the jq parse above targets. When jq returns empty, the stdin may in
|
|
66
|
+
# fact be git's pre-push ref-list — sniff the first non-blank line, and if it
|
|
67
|
+
# matches the `<ref> <40-hex> <ref> <40-hex>` shape, synthesize CMD as
|
|
68
|
+
# `git push <remote>` (from argv $1) so the remainder of the gate runs
|
|
69
|
+
# through the pre-push parser in step 6 rather than the argv fallback.
|
|
70
|
+
#
|
|
71
|
+
# Any other stdin shape (empty, random JSON, a non-push tool call) still
|
|
72
|
+
# exits 0 here — the gate is a no-op for non-push Bash calls by design.
|
|
73
|
+
FIRST_STDIN_LINE=$(printf '%s' "$INPUT" | awk 'NF { print; exit }')
|
|
59
74
|
if [[ -z "$CMD" ]]; then
|
|
60
|
-
|
|
75
|
+
if [[ -n "$FIRST_STDIN_LINE" ]] \
|
|
76
|
+
&& printf '%s' "$FIRST_STDIN_LINE" \
|
|
77
|
+
| grep -qE '^[^[:space:]]+[[:space:]]+[0-9a-f]{40}[[:space:]]+[^[:space:]]+[[:space:]]+[0-9a-f]{40}[[:space:]]*$'; then
|
|
78
|
+
# Git native pre-push path. Remote comes from argv $1 — falls back to
|
|
79
|
+
# `origin` for safety if the hook was invoked without arguments.
|
|
80
|
+
CMD="git push ${1:-origin}"
|
|
81
|
+
else
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
61
84
|
fi
|
|
62
85
|
|
|
63
86
|
# Only trigger on git push commands
|
|
@@ -73,6 +96,167 @@ if [[ -f "$POLICY_FILE" ]]; then
|
|
|
73
96
|
fi
|
|
74
97
|
fi
|
|
75
98
|
|
|
99
|
+
# ── 5a. REA_SKIP_PUSH_REVIEW — whole-gate escape hatch ───────────────────────
|
|
100
|
+
# An opt-in bypass for the ENTIRE push-review gate (not just the Codex branch).
|
|
101
|
+
# Exists to unblock consumers when rea itself is broken (as in BUG-009 pre-0.5.0)
|
|
102
|
+
# or a corrupt policy/audit file would otherwise deadlock a push. Requires an
|
|
103
|
+
# explicit non-empty reason; the value of REA_SKIP_PUSH_REVIEW is recorded
|
|
104
|
+
# verbatim in the audit record as the reason.
|
|
105
|
+
#
|
|
106
|
+
# Fail-closed contract matches REA_SKIP_CODEX_REVIEW:
|
|
107
|
+
# - missing dist/audit/append.js → exit 2
|
|
108
|
+
# - missing git identity → exit 2
|
|
109
|
+
# - Node failure → exit 2
|
|
110
|
+
#
|
|
111
|
+
# Audit tool_name is `push.review.skipped`. This is intentionally NOT
|
|
112
|
+
# `codex.review` or `codex.review.skipped` — a skip of the whole gate is a
|
|
113
|
+
# separately-audited event and does not satisfy the Codex-review jq predicate.
|
|
114
|
+
if [[ -n "${REA_SKIP_PUSH_REVIEW:-}" ]]; then
|
|
115
|
+
SKIP_REASON="$REA_SKIP_PUSH_REVIEW"
|
|
116
|
+
AUDIT_APPEND_JS="${REA_ROOT}/dist/audit/append.js"
|
|
117
|
+
|
|
118
|
+
if [[ ! -f "$AUDIT_APPEND_JS" ]]; then
|
|
119
|
+
{
|
|
120
|
+
printf 'PUSH BLOCKED: REA_SKIP_PUSH_REVIEW requires rea to be built.\n'
|
|
121
|
+
printf '\n'
|
|
122
|
+
printf ' REA_SKIP_PUSH_REVIEW is set but %s is missing.\n' "$AUDIT_APPEND_JS"
|
|
123
|
+
printf ' Run: pnpm build\n'
|
|
124
|
+
printf '\n'
|
|
125
|
+
} >&2
|
|
126
|
+
exit 2
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# Codex F2: CI-aware refusal. The skip hatch is ambient — any process that
|
|
130
|
+
# can set env vars can flip the gate off with a forged git identity (git
|
|
131
|
+
# config is mutable repo config). In a CI context, refuse by default; only
|
|
132
|
+
# allow if the policy explicitly opted in via review.allow_skip_in_ci=true.
|
|
133
|
+
if [[ -n "${CI:-}" ]]; then
|
|
134
|
+
ALLOW_CI_SKIP=""
|
|
135
|
+
READ_FIELD_JS="${REA_ROOT}/dist/scripts/read-policy-field.js"
|
|
136
|
+
if [[ -f "$READ_FIELD_JS" ]]; then
|
|
137
|
+
ALLOW_CI_SKIP=$(REA_ROOT="$REA_ROOT" node "$READ_FIELD_JS" review.allow_skip_in_ci 2>/dev/null || echo "")
|
|
138
|
+
fi
|
|
139
|
+
if [[ "$ALLOW_CI_SKIP" != "true" ]]; then
|
|
140
|
+
{
|
|
141
|
+
printf 'PUSH BLOCKED: REA_SKIP_PUSH_REVIEW refused in CI context.\n'
|
|
142
|
+
printf '\n'
|
|
143
|
+
printf ' CI env var is set. An unauthenticated env-var bypass in a shared\n'
|
|
144
|
+
printf ' build agent is not trusted. To enable, set\n'
|
|
145
|
+
printf ' review:\n'
|
|
146
|
+
printf ' allow_skip_in_ci: true\n'
|
|
147
|
+
printf ' in .rea/policy.yaml — explicitly authorizing env-var skips in CI.\n'
|
|
148
|
+
printf '\n'
|
|
149
|
+
} >&2
|
|
150
|
+
exit 2
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
SKIP_ACTOR=$(cd "$REA_ROOT" && git config user.email 2>/dev/null || echo "")
|
|
155
|
+
if [[ -z "$SKIP_ACTOR" ]]; then
|
|
156
|
+
SKIP_ACTOR=$(cd "$REA_ROOT" && git config user.name 2>/dev/null || echo "")
|
|
157
|
+
fi
|
|
158
|
+
if [[ -z "$SKIP_ACTOR" ]]; then
|
|
159
|
+
{
|
|
160
|
+
printf 'PUSH BLOCKED: REA_SKIP_PUSH_REVIEW requires a git identity.\n'
|
|
161
|
+
printf '\n'
|
|
162
|
+
# shellcheck disable=SC2016 # backticks are literal markdown in user-facing message
|
|
163
|
+
printf ' Neither `git config user.email` nor `git config user.name`\n'
|
|
164
|
+
printf ' is set. The skip audit record would have no actor; refusing\n'
|
|
165
|
+
printf ' to bypass without one.\n'
|
|
166
|
+
printf '\n'
|
|
167
|
+
} >&2
|
|
168
|
+
exit 2
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
SKIP_BRANCH=$(cd "$REA_ROOT" && git branch --show-current 2>/dev/null || echo "")
|
|
172
|
+
SKIP_HEAD=$(cd "$REA_ROOT" && git rev-parse HEAD 2>/dev/null || echo "")
|
|
173
|
+
|
|
174
|
+
# Codex F2: record OS identity alongside the (mutable, git-sourced) actor so
|
|
175
|
+
# downstream auditors can reconstruct who REALLY invoked the bypass on a
|
|
176
|
+
# shared host. None of these are forgeable from inside the push process alone.
|
|
177
|
+
SKIP_OS_UID=$(id -u 2>/dev/null || echo "")
|
|
178
|
+
SKIP_OS_WHOAMI=$(whoami 2>/dev/null || echo "")
|
|
179
|
+
SKIP_OS_HOST=$(hostname 2>/dev/null || echo "")
|
|
180
|
+
SKIP_OS_PID=$$
|
|
181
|
+
SKIP_OS_PPID=$PPID
|
|
182
|
+
SKIP_OS_PPID_CMD=$(ps -o command= -p "$PPID" 2>/dev/null | head -c 512 || echo "")
|
|
183
|
+
SKIP_OS_TTY=$(tty 2>/dev/null || echo "not-a-tty")
|
|
184
|
+
SKIP_OS_CI="${CI:-}"
|
|
185
|
+
|
|
186
|
+
SKIP_METADATA=$(jq -n \
|
|
187
|
+
--arg head_sha "$SKIP_HEAD" \
|
|
188
|
+
--arg branch "$SKIP_BRANCH" \
|
|
189
|
+
--arg reason "$SKIP_REASON" \
|
|
190
|
+
--arg actor "$SKIP_ACTOR" \
|
|
191
|
+
--arg os_uid "$SKIP_OS_UID" \
|
|
192
|
+
--arg os_whoami "$SKIP_OS_WHOAMI" \
|
|
193
|
+
--arg os_hostname "$SKIP_OS_HOST" \
|
|
194
|
+
--arg os_pid "$SKIP_OS_PID" \
|
|
195
|
+
--arg os_ppid "$SKIP_OS_PPID" \
|
|
196
|
+
--arg os_ppid_cmd "$SKIP_OS_PPID_CMD" \
|
|
197
|
+
--arg os_tty "$SKIP_OS_TTY" \
|
|
198
|
+
--arg os_ci "$SKIP_OS_CI" \
|
|
199
|
+
'{
|
|
200
|
+
head_sha: $head_sha,
|
|
201
|
+
branch: $branch,
|
|
202
|
+
reason: $reason,
|
|
203
|
+
actor: $actor,
|
|
204
|
+
verdict: "skipped",
|
|
205
|
+
os_identity: {
|
|
206
|
+
uid: $os_uid,
|
|
207
|
+
whoami: $os_whoami,
|
|
208
|
+
hostname: $os_hostname,
|
|
209
|
+
pid: $os_pid,
|
|
210
|
+
ppid: $os_ppid,
|
|
211
|
+
ppid_cmd: $os_ppid_cmd,
|
|
212
|
+
tty: $os_tty,
|
|
213
|
+
ci: $os_ci
|
|
214
|
+
}
|
|
215
|
+
}' 2>/dev/null)
|
|
216
|
+
|
|
217
|
+
if [[ -z "$SKIP_METADATA" ]]; then
|
|
218
|
+
{
|
|
219
|
+
printf 'PUSH BLOCKED: REA_SKIP_PUSH_REVIEW could not serialize audit metadata.\n' >&2
|
|
220
|
+
} >&2
|
|
221
|
+
exit 2
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
REA_ROOT="$REA_ROOT" REA_SKIP_METADATA="$SKIP_METADATA" \
|
|
225
|
+
node --input-type=module -e "
|
|
226
|
+
const mod = await import(process.env.REA_ROOT + '/dist/audit/append.js');
|
|
227
|
+
const metadata = JSON.parse(process.env.REA_SKIP_METADATA);
|
|
228
|
+
await mod.appendAuditRecord(process.env.REA_ROOT, {
|
|
229
|
+
tool_name: 'push.review.skipped',
|
|
230
|
+
server_name: 'rea.escape_hatch',
|
|
231
|
+
status: mod.InvocationStatus.Allowed,
|
|
232
|
+
tier: mod.Tier.Read,
|
|
233
|
+
metadata,
|
|
234
|
+
});
|
|
235
|
+
" 2>/dev/null
|
|
236
|
+
NODE_STATUS=$?
|
|
237
|
+
if [[ "$NODE_STATUS" -ne 0 ]]; then
|
|
238
|
+
{
|
|
239
|
+
printf 'PUSH BLOCKED: REA_SKIP_PUSH_REVIEW audit-append failed (node exit %s).\n' "$NODE_STATUS"
|
|
240
|
+
printf ' Refusing to bypass the push gate without a receipt.\n'
|
|
241
|
+
} >&2
|
|
242
|
+
exit 2
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
{
|
|
246
|
+
printf '\n'
|
|
247
|
+
printf '== PUSH REVIEW GATE SKIPPED via REA_SKIP_PUSH_REVIEW\n'
|
|
248
|
+
printf ' Reason: %s\n' "$SKIP_REASON"
|
|
249
|
+
printf ' Actor: %s\n' "$SKIP_ACTOR"
|
|
250
|
+
printf ' Branch: %s\n' "${SKIP_BRANCH:-<detached>}"
|
|
251
|
+
printf ' Head: %s\n' "${SKIP_HEAD:-<unknown>}"
|
|
252
|
+
printf ' Audited: .rea/audit.jsonl (tool_name=push.review.skipped)\n'
|
|
253
|
+
printf '\n'
|
|
254
|
+
printf ' This is a gate weakening. Every invocation is permanently audited.\n'
|
|
255
|
+
printf '\n'
|
|
256
|
+
} >&2
|
|
257
|
+
exit 0
|
|
258
|
+
fi
|
|
259
|
+
|
|
76
260
|
# ── 6. Determine source/target commits for each refspec ──────────────────────
|
|
77
261
|
# The authoritative source for which commits are being pushed is the pre-push
|
|
78
262
|
# hook stdin contract: one line per refspec, with fields
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bookedsolid/rea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and Codex-integrated adversarial review for AI-assisted projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Booked Solid Technology <oss@bookedsolid.tech> (https://bookedsolid.tech)",
|