@bookedsolid/rea 0.10.3 → 0.11.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/.husky/pre-push +22 -167
- package/agents/codex-adversarial.md +5 -3
- package/commands/codex-review.md +3 -5
- package/dist/audit/append.d.ts +7 -32
- package/dist/audit/append.js +7 -35
- package/dist/cli/audit.d.ts +0 -31
- package/dist/cli/audit.js +5 -74
- package/dist/cli/doctor.js +6 -16
- package/dist/cli/hook.d.ts +48 -0
- package/dist/cli/hook.js +127 -0
- package/dist/cli/index.js +5 -80
- package/dist/cli/init.js +1 -1
- package/dist/cli/install/gitignore.d.ts +2 -2
- package/dist/cli/install/gitignore.js +3 -3
- package/dist/cli/install/pre-push.d.ts +146 -271
- package/dist/cli/install/pre-push.js +471 -2633
- package/dist/cli/install/settings-merge.d.ts +17 -0
- package/dist/cli/install/settings-merge.js +48 -1
- package/dist/cli/upgrade.js +131 -3
- package/dist/config/tier-map.js +18 -25
- package/dist/hooks/push-gate/base.d.ts +57 -0
- package/dist/hooks/push-gate/base.js +77 -0
- package/dist/hooks/push-gate/codex-runner.d.ts +126 -0
- package/dist/hooks/push-gate/codex-runner.js +223 -0
- package/dist/hooks/push-gate/findings.d.ts +68 -0
- package/dist/hooks/push-gate/findings.js +142 -0
- package/dist/hooks/push-gate/halt.d.ts +28 -0
- package/dist/hooks/push-gate/halt.js +49 -0
- package/dist/hooks/push-gate/index.d.ts +90 -0
- package/dist/hooks/push-gate/index.js +351 -0
- package/dist/hooks/push-gate/policy.d.ts +41 -0
- package/dist/hooks/push-gate/policy.js +55 -0
- package/dist/hooks/push-gate/report.d.ts +89 -0
- package/dist/hooks/push-gate/report.js +140 -0
- package/dist/policy/loader.d.ts +10 -10
- package/dist/policy/loader.js +7 -6
- package/dist/policy/types.d.ts +31 -22
- package/package.json +1 -1
- package/dist/cache/review-cache.d.ts +0 -115
- package/dist/cache/review-cache.js +0 -200
- package/dist/cli/cache.d.ts +0 -84
- package/dist/cli/cache.js +0 -150
- package/dist/hooks/review-gate/args.d.ts +0 -126
- package/dist/hooks/review-gate/args.js +0 -315
- package/dist/hooks/review-gate/audit.d.ts +0 -131
- package/dist/hooks/review-gate/audit.js +0 -181
- package/dist/hooks/review-gate/banner.d.ts +0 -97
- package/dist/hooks/review-gate/banner.js +0 -172
- package/dist/hooks/review-gate/base-resolve.d.ts +0 -155
- package/dist/hooks/review-gate/base-resolve.js +0 -247
- package/dist/hooks/review-gate/cache-key.d.ts +0 -55
- package/dist/hooks/review-gate/cache-key.js +0 -41
- package/dist/hooks/review-gate/cache.d.ts +0 -108
- package/dist/hooks/review-gate/cache.js +0 -120
- package/dist/hooks/review-gate/constants.d.ts +0 -26
- package/dist/hooks/review-gate/constants.js +0 -34
- package/dist/hooks/review-gate/diff.d.ts +0 -181
- package/dist/hooks/review-gate/diff.js +0 -232
- package/dist/hooks/review-gate/errors.d.ts +0 -72
- package/dist/hooks/review-gate/errors.js +0 -100
- package/dist/hooks/review-gate/hash.d.ts +0 -43
- package/dist/hooks/review-gate/hash.js +0 -46
- package/dist/hooks/review-gate/index.d.ts +0 -31
- package/dist/hooks/review-gate/index.js +0 -35
- package/dist/hooks/review-gate/metadata.d.ts +0 -98
- package/dist/hooks/review-gate/metadata.js +0 -158
- package/dist/hooks/review-gate/policy.d.ts +0 -55
- package/dist/hooks/review-gate/policy.js +0 -71
- package/dist/hooks/review-gate/protected-paths.d.ts +0 -46
- package/dist/hooks/review-gate/protected-paths.js +0 -76
- package/hooks/_lib/push-review-core.sh +0 -1250
- package/hooks/commit-review-gate.sh +0 -330
- package/hooks/push-review-gate-git.sh +0 -94
- package/hooks/push-review-gate.sh +0 -92
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed error set for the review-gate modules (G — push-review/commit-review
|
|
3
|
-
* TypeScript port).
|
|
4
|
-
*
|
|
5
|
-
* The bash core signals outcomes via exit codes + stderr banners. The TS port
|
|
6
|
-
* expresses each outcome as a typed error so callers can branch on class
|
|
7
|
-
* instead of parsing banner text, and so the eventual CLI entry point can
|
|
8
|
-
* translate a thrown error into the same exit code + banner the bash core
|
|
9
|
-
* emitted (preserving external contract per design §2, non-goals).
|
|
10
|
-
*
|
|
11
|
-
* Every error subclass carries:
|
|
12
|
-
* - a stable `code` (for programmatic dispatch in tests + the CLI shim)
|
|
13
|
-
* - an `exitCode` (matches the bash core's `exit N` semantics)
|
|
14
|
-
* - the operator-facing `message` composed by `banner.ts`
|
|
15
|
-
*
|
|
16
|
-
* This module is intentionally dependency-free so unit tests can import it
|
|
17
|
-
* without dragging in fs/child_process. The CLI entry point is the single
|
|
18
|
-
* place that translates these to `process.exit(N)`.
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Base class. All review-gate errors derive from this so a CLI dispatch layer
|
|
22
|
-
* can `catch (e) { if (e instanceof ReviewGateError) ... }`.
|
|
23
|
-
*/
|
|
24
|
-
export class ReviewGateError extends Error {
|
|
25
|
-
code;
|
|
26
|
-
exitCode;
|
|
27
|
-
details;
|
|
28
|
-
constructor(code, message, exitCode, details = {}) {
|
|
29
|
-
super(message);
|
|
30
|
-
this.name = 'ReviewGateError';
|
|
31
|
-
this.code = code;
|
|
32
|
-
this.exitCode = exitCode;
|
|
33
|
-
this.details = details;
|
|
34
|
-
// Node gives us a stable prototype chain via Error; no need to hack it.
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Blocked-push errors (exit 2). The bash core uses exit 2 for every blocked
|
|
39
|
-
* condition; we preserve that invariant so the shim's exit code is unchanged.
|
|
40
|
-
*/
|
|
41
|
-
export class BlockedError extends ReviewGateError {
|
|
42
|
-
constructor(code, message, details = {}) {
|
|
43
|
-
super(code, message, 2, details);
|
|
44
|
-
this.name = 'BlockedError';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Deletion detected anywhere in the push (defect J). Must fail-closed even
|
|
49
|
-
* when a sibling refspec would have been reviewable.
|
|
50
|
-
*/
|
|
51
|
-
export class DeletionBlockedError extends BlockedError {
|
|
52
|
-
constructor() {
|
|
53
|
-
super('PUSH_BLOCKED_DELETE', 'refspec is a branch deletion.\n' +
|
|
54
|
-
'\n' +
|
|
55
|
-
' Branch deletions are sensitive operations and require explicit\n' +
|
|
56
|
-
' human action outside the agent. Perform the deletion manually.\n');
|
|
57
|
-
this.name = 'DeletionBlockedError';
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* A refspec with `HEAD` as destination is operator error (design §3.1,
|
|
62
|
-
* `pr_resolve_argv_refspecs`). Carry the refspec in details for banner render.
|
|
63
|
-
*/
|
|
64
|
-
export class HeadRefspecBlockedError extends BlockedError {
|
|
65
|
-
constructor(spec) {
|
|
66
|
-
super('PUSH_BLOCKED_HEAD_REFSPEC', `refspec resolves to HEAD (from ${JSON.stringify(spec)})\n` +
|
|
67
|
-
'\n' +
|
|
68
|
-
' `git push <remote> HEAD:<branch>` or similar is almost always\n' +
|
|
69
|
-
' operator error in this context. Name the destination branch\n' +
|
|
70
|
-
' explicitly so the review gate can diff against it.\n', { spec });
|
|
71
|
-
this.name = 'HeadRefspecBlockedError';
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Invalid `--delete` refspec (empty destination or HEAD destination).
|
|
76
|
-
* Distinct from the general HEAD-refspec error because bash emits a
|
|
77
|
-
* different operator banner for the delete-mode case — the remediation
|
|
78
|
-
* text is "name the branch you meant to delete" rather than the HEAD
|
|
79
|
-
* destination error. See push-review-core.sh §161-168.
|
|
80
|
-
*/
|
|
81
|
-
export class InvalidDeleteRefspecError extends BlockedError {
|
|
82
|
-
constructor(spec) {
|
|
83
|
-
super('PUSH_BLOCKED_HEAD_REFSPEC', `--delete refspec resolves to HEAD or empty (from ${JSON.stringify(spec)})\n`, { spec, mode: 'delete' });
|
|
84
|
-
this.name = 'InvalidDeleteRefspecError';
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Defect N completion (landed in phase 4, type reserved in phase 1 so
|
|
89
|
-
* `base-resolve.ts` can throw it later without schema churn).
|
|
90
|
-
*/
|
|
91
|
-
export class NoBaseResolvableError extends BlockedError {
|
|
92
|
-
constructor(source) {
|
|
93
|
-
super('PUSH_BLOCKED_NO_BASE_RESOLVABLE', `cannot resolve base branch for ${source}; run ` +
|
|
94
|
-
'`git branch --set-upstream-to=origin/<target>` or ' +
|
|
95
|
-
'`git config branch.' +
|
|
96
|
-
source +
|
|
97
|
-
'.base <ref>`', { source });
|
|
98
|
-
this.name = 'NoBaseResolvableError';
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Portable SHA-256 over arbitrary strings. Replaces the bash core's
|
|
3
|
-
* sha256sum → shasum → openssl fallback chain (defect L) with a single
|
|
4
|
-
* Node-stdlib call, removing the Alpine/distroless "no hasher on PATH"
|
|
5
|
-
* regression class entirely.
|
|
6
|
-
*
|
|
7
|
-
* ## Why a dedicated module
|
|
8
|
-
*
|
|
9
|
-
* The cache-key contract (design §8) requires byte-exact parity with the
|
|
10
|
-
* 0.10.1 bash implementation. The bash implementation computes
|
|
11
|
-
* `sha256sum( <full git diff> )` and uses the hex digest as the cache key.
|
|
12
|
-
* `crypto.createHash('sha256').update(s).digest('hex')` is bit-identical to
|
|
13
|
-
* GNU `sha256sum < <(printf '%s' s)` output (neither includes the
|
|
14
|
-
* filename-suffix padding). Regression-tested against the fixture in
|
|
15
|
-
* `__fixtures__/cache-keys.json`.
|
|
16
|
-
*
|
|
17
|
-
* ## Hex-64 validation
|
|
18
|
-
*
|
|
19
|
-
* The bash core validates the hasher output is `^[0-9a-f]{64}$` before using
|
|
20
|
-
* it as a cache key; a partial read or broken pipe would otherwise cache
|
|
21
|
-
* garbage. Node's `createHash` is synchronous and crypto-backed, so the
|
|
22
|
-
* digest is always a valid hex-64. We preserve the validation helper for
|
|
23
|
-
* any future path where user-supplied strings might be treated as SHAs (the
|
|
24
|
-
* bash core does this for push_sha env-pass, which the TS port rejects at
|
|
25
|
-
* the type level instead).
|
|
26
|
-
*/
|
|
27
|
-
/** A hex-lowercased SHA-256 digest (64 chars). */
|
|
28
|
-
export type HexSha256 = string;
|
|
29
|
-
/**
|
|
30
|
-
* Compute a SHA-256 over the UTF-8 bytes of `input`. Returns the lowercase
|
|
31
|
-
* hex digest.
|
|
32
|
-
*
|
|
33
|
-
* This is the one function the cache-key contract depends on — never change
|
|
34
|
-
* the encoding or the digest format without bumping the cache-key version
|
|
35
|
-
* (which none of phases 1–4 are permitted to do, per design §8).
|
|
36
|
-
*/
|
|
37
|
-
export declare function sha256Hex(input: string): HexSha256;
|
|
38
|
-
/**
|
|
39
|
-
* True iff the input looks like a canonical lowercase SHA-256 hex digest.
|
|
40
|
-
* Used by tests and by defensive callers that accept a string and want to
|
|
41
|
-
* reject malformed input before writing it into the cache.
|
|
42
|
-
*/
|
|
43
|
-
export declare function isValidSha256Hex(value: string): value is HexSha256;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Portable SHA-256 over arbitrary strings. Replaces the bash core's
|
|
3
|
-
* sha256sum → shasum → openssl fallback chain (defect L) with a single
|
|
4
|
-
* Node-stdlib call, removing the Alpine/distroless "no hasher on PATH"
|
|
5
|
-
* regression class entirely.
|
|
6
|
-
*
|
|
7
|
-
* ## Why a dedicated module
|
|
8
|
-
*
|
|
9
|
-
* The cache-key contract (design §8) requires byte-exact parity with the
|
|
10
|
-
* 0.10.1 bash implementation. The bash implementation computes
|
|
11
|
-
* `sha256sum( <full git diff> )` and uses the hex digest as the cache key.
|
|
12
|
-
* `crypto.createHash('sha256').update(s).digest('hex')` is bit-identical to
|
|
13
|
-
* GNU `sha256sum < <(printf '%s' s)` output (neither includes the
|
|
14
|
-
* filename-suffix padding). Regression-tested against the fixture in
|
|
15
|
-
* `__fixtures__/cache-keys.json`.
|
|
16
|
-
*
|
|
17
|
-
* ## Hex-64 validation
|
|
18
|
-
*
|
|
19
|
-
* The bash core validates the hasher output is `^[0-9a-f]{64}$` before using
|
|
20
|
-
* it as a cache key; a partial read or broken pipe would otherwise cache
|
|
21
|
-
* garbage. Node's `createHash` is synchronous and crypto-backed, so the
|
|
22
|
-
* digest is always a valid hex-64. We preserve the validation helper for
|
|
23
|
-
* any future path where user-supplied strings might be treated as SHAs (the
|
|
24
|
-
* bash core does this for push_sha env-pass, which the TS port rejects at
|
|
25
|
-
* the type level instead).
|
|
26
|
-
*/
|
|
27
|
-
import { createHash } from 'node:crypto';
|
|
28
|
-
/**
|
|
29
|
-
* Compute a SHA-256 over the UTF-8 bytes of `input`. Returns the lowercase
|
|
30
|
-
* hex digest.
|
|
31
|
-
*
|
|
32
|
-
* This is the one function the cache-key contract depends on — never change
|
|
33
|
-
* the encoding or the digest format without bumping the cache-key version
|
|
34
|
-
* (which none of phases 1–4 are permitted to do, per design §8).
|
|
35
|
-
*/
|
|
36
|
-
export function sha256Hex(input) {
|
|
37
|
-
return createHash('sha256').update(input, 'utf8').digest('hex');
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* True iff the input looks like a canonical lowercase SHA-256 hex digest.
|
|
41
|
-
* Used by tests and by defensive callers that accept a string and want to
|
|
42
|
-
* reject malformed input before writing it into the cache.
|
|
43
|
-
*/
|
|
44
|
-
export function isValidSha256Hex(value) {
|
|
45
|
-
return /^[0-9a-f]{64}$/.test(value);
|
|
46
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public entry point for the review-gate TypeScript port (G).
|
|
3
|
-
*
|
|
4
|
-
* ## Scope after Phase 2a (0.10.3)
|
|
5
|
-
*
|
|
6
|
-
* - Phase 1 primitives (args, banner, cache-key, constants, errors,
|
|
7
|
-
* hash, metadata, policy, protected-paths) — pure, dependency-free.
|
|
8
|
-
* - Phase 2a supporting modules (base-resolve, diff, audit, cache) —
|
|
9
|
-
* wrap git subprocesses, wrap audit append/scan, wrap the review-
|
|
10
|
-
* cache lookup. No top-level gate yet; composition lands in Phase 2b.
|
|
11
|
-
*
|
|
12
|
-
* The bash core in `hooks/_lib/push-review-core.sh` continues to run in
|
|
13
|
-
* production until phase 4. These exports are library-level primitives
|
|
14
|
-
* that tests and Phase 2b compose; no behavioral surface is registered
|
|
15
|
-
* for external callers here.
|
|
16
|
-
*
|
|
17
|
-
* See `docs/design/push-review-ts-port.md` for the full plan.
|
|
18
|
-
*/
|
|
19
|
-
export * from './args.js';
|
|
20
|
-
export * from './banner.js';
|
|
21
|
-
export * from './cache-key.js';
|
|
22
|
-
export * from './constants.js';
|
|
23
|
-
export * from './errors.js';
|
|
24
|
-
export * from './hash.js';
|
|
25
|
-
export * from './metadata.js';
|
|
26
|
-
export * from './policy.js';
|
|
27
|
-
export * from './protected-paths.js';
|
|
28
|
-
export { currentBranch, diffNameStatus, fullDiff, gitCommonDir, hasCommitLocally, mergeBase, readGitActor, readGitConfig, refExists, resolveHead, resolveRefToSha, resolveRemoteDefaultRef, resolveUpstream, revListCount, spawnGit, type DiffResult, type GitRunResult, type GitRunner, type NameStatusResult, } from './diff.js';
|
|
29
|
-
export { computeInitialTargetLabel, resolveBaseForRefspec, resolveNewBranchBase, stripRefsHeadsOnly, type ResolveBaseDeps, type ResolvedBase, } from './base-resolve.js';
|
|
30
|
-
export { CODEX_REVIEW_SKIPPED_TOOL, ESCAPE_HATCH_SERVER, PUSH_REVIEW_CACHE_ERROR_TOOL, PUSH_REVIEW_CACHE_HIT_TOOL, PUSH_REVIEW_SERVER, PUSH_REVIEW_SKIPPED_TOOL, emitCodexReviewSkipped, emitPushReviewSkipped, hasValidCodexReview, isQualifyingCodexReview, type SkipCodexReviewAuditInput, type SkipPushReviewAuditInput, } from './audit.js';
|
|
31
|
-
export { checkReviewCache, type CacheOutcome, type CheckReviewCacheInput, } from './cache.js';
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public entry point for the review-gate TypeScript port (G).
|
|
3
|
-
*
|
|
4
|
-
* ## Scope after Phase 2a (0.10.3)
|
|
5
|
-
*
|
|
6
|
-
* - Phase 1 primitives (args, banner, cache-key, constants, errors,
|
|
7
|
-
* hash, metadata, policy, protected-paths) — pure, dependency-free.
|
|
8
|
-
* - Phase 2a supporting modules (base-resolve, diff, audit, cache) —
|
|
9
|
-
* wrap git subprocesses, wrap audit append/scan, wrap the review-
|
|
10
|
-
* cache lookup. No top-level gate yet; composition lands in Phase 2b.
|
|
11
|
-
*
|
|
12
|
-
* The bash core in `hooks/_lib/push-review-core.sh` continues to run in
|
|
13
|
-
* production until phase 4. These exports are library-level primitives
|
|
14
|
-
* that tests and Phase 2b compose; no behavioral surface is registered
|
|
15
|
-
* for external callers here.
|
|
16
|
-
*
|
|
17
|
-
* See `docs/design/push-review-ts-port.md` for the full plan.
|
|
18
|
-
*/
|
|
19
|
-
// Phase 1 primitives
|
|
20
|
-
export * from './args.js';
|
|
21
|
-
export * from './banner.js';
|
|
22
|
-
export * from './cache-key.js';
|
|
23
|
-
export * from './constants.js';
|
|
24
|
-
export * from './errors.js';
|
|
25
|
-
export * from './hash.js';
|
|
26
|
-
export * from './metadata.js';
|
|
27
|
-
export * from './policy.js';
|
|
28
|
-
export * from './protected-paths.js';
|
|
29
|
-
// Phase 2a supporting modules — re-export explicit names to avoid
|
|
30
|
-
// double-exporting `computeCacheKey` (which lives in both cache-key.ts
|
|
31
|
-
// and cache.ts; cache.ts's is a strict re-export of Phase 1's).
|
|
32
|
-
export { currentBranch, diffNameStatus, fullDiff, gitCommonDir, hasCommitLocally, mergeBase, readGitActor, readGitConfig, refExists, resolveHead, resolveRefToSha, resolveRemoteDefaultRef, resolveUpstream, revListCount, spawnGit, } from './diff.js';
|
|
33
|
-
export { computeInitialTargetLabel, resolveBaseForRefspec, resolveNewBranchBase, stripRefsHeadsOnly, } from './base-resolve.js';
|
|
34
|
-
export { CODEX_REVIEW_SKIPPED_TOOL, ESCAPE_HATCH_SERVER, PUSH_REVIEW_CACHE_ERROR_TOOL, PUSH_REVIEW_CACHE_HIT_TOOL, PUSH_REVIEW_SERVER, PUSH_REVIEW_SKIPPED_TOOL, emitCodexReviewSkipped, emitPushReviewSkipped, hasValidCodexReview, isQualifyingCodexReview, } from './audit.js';
|
|
35
|
-
export { checkReviewCache, } from './cache.js';
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OS / agent / repo identity collection for audit records.
|
|
3
|
-
*
|
|
4
|
-
* Closes defect M (0.9.4): the bash core's `jq --arg os_pid "$PID"` wrote
|
|
5
|
-
* the pid as a JSON string; the audit consumer expected an integer, and the
|
|
6
|
-
* schema had to tolerate both. The TS port emits pid/ppid as numbers from
|
|
7
|
-
* day one (the JS runtime's `process.pid` is already a `number`), removing
|
|
8
|
-
* the whole class of jq `--arg` vs `--argjson` confusion.
|
|
9
|
-
*
|
|
10
|
-
* ## Why capture this at all
|
|
11
|
-
*
|
|
12
|
-
* A skip-audit record (`REA_SKIP_PUSH_REVIEW` / `REA_SKIP_CODEX_REVIEW`) is
|
|
13
|
-
* the one place the gate is voluntarily weakened. The actor field (from
|
|
14
|
-
* `git config user.email`) is mutable — a process with write access to
|
|
15
|
-
* `.git/config` can stamp any email it likes. Supplementing that with
|
|
16
|
-
* non-forgeable host-level identity (uid, hostname, pid, ppid, tty, CI
|
|
17
|
-
* flag) gives a forensic investigator something to cross-reference when
|
|
18
|
-
* a skip record turns out to have been unauthorized.
|
|
19
|
-
*
|
|
20
|
-
* ## Determinism + testability
|
|
21
|
-
*
|
|
22
|
-
* Every collector is a plain function over `process` / `os` / `node:child_process`
|
|
23
|
-
* so tests can stub the collector's inputs cleanly. The public API takes no
|
|
24
|
-
* arguments (production use) and the helpers are exported for tests.
|
|
25
|
-
*/
|
|
26
|
-
export interface OsIdentity {
|
|
27
|
-
/** POSIX uid as a string (empty when not available — Windows). */
|
|
28
|
-
uid: string;
|
|
29
|
-
/** `whoami` output — username or empty string. */
|
|
30
|
-
whoami: string;
|
|
31
|
-
/** `hostname` output. */
|
|
32
|
-
hostname: string;
|
|
33
|
-
/** This process's pid — number, not string (defect M). */
|
|
34
|
-
pid: number;
|
|
35
|
-
/** Parent process's pid — number, not string (defect M). */
|
|
36
|
-
ppid: number;
|
|
37
|
-
/** `ps -o command= -p $PPID` output, capped at 512 bytes. */
|
|
38
|
-
ppid_cmd: string;
|
|
39
|
-
/** `tty` output or `"not-a-tty"`. */
|
|
40
|
-
tty: string;
|
|
41
|
-
/** `CI` env var value or empty string. */
|
|
42
|
-
ci: string;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Collect current-process OS identity. Every individual collector degrades
|
|
46
|
-
* to an empty-string fallback on any exception — matching the bash core's
|
|
47
|
-
* `id -u || echo ""`, `whoami || echo ""`, `hostname || echo ""` pattern
|
|
48
|
-
* (push-review-core.sh §420-422, 426). Codex pass-1 on phase 1 flagged
|
|
49
|
-
* the earlier implementation that called `userInfo()` and `hostname()`
|
|
50
|
-
* outside any try/catch and could therefore throw on hosts with broken
|
|
51
|
-
* NSS/passwd lookups, which would silently block the skip-audit path.
|
|
52
|
-
*/
|
|
53
|
-
export declare function collectOsIdentity(): OsIdentity;
|
|
54
|
-
/**
|
|
55
|
-
* Read POSIX uid + whoami. Each field is collected INDEPENDENTLY so a
|
|
56
|
-
* partial-lookup-failure (e.g. LDAP/NSS returns the uid but no passwd
|
|
57
|
-
* entry) still yields one field rather than dropping both. Bash-core
|
|
58
|
-
* parity (push-review-core.sh §420-421): `id -u` and `whoami` are two
|
|
59
|
-
* separate invocations, each with its own `|| echo ""` fallback.
|
|
60
|
-
*
|
|
61
|
-
* Codex pass-2 on phase 1 flagged the prior single-`userInfo()` version:
|
|
62
|
-
* a broken NSS lookup zeroed out both fields at once, weakening forensic
|
|
63
|
-
* metadata on shared hosts. We now prefer the POSIX primitives
|
|
64
|
-
* (`os.userInfo()` internally reads the passwd entry) but isolate the
|
|
65
|
-
* failures so uid and whoami cannot both disappear together when only
|
|
66
|
-
* one of them is actually unavailable.
|
|
67
|
-
*/
|
|
68
|
-
export declare function readUidAndWhoami(): {
|
|
69
|
-
uid: string;
|
|
70
|
-
whoami: string;
|
|
71
|
-
};
|
|
72
|
-
/**
|
|
73
|
-
* Read `hostname` via `os.hostname`. Returns an empty string on any error.
|
|
74
|
-
* Exported for unit tests.
|
|
75
|
-
*/
|
|
76
|
-
export declare function readHostname(): string;
|
|
77
|
-
/**
|
|
78
|
-
* Read `ps -o command= -p <ppid>` safely. Returns the (truncated) command
|
|
79
|
-
* or an empty string on any failure.
|
|
80
|
-
*
|
|
81
|
-
* Security: args are passed as an array, never interpolated into a shell
|
|
82
|
-
* string. `ps` is the only executable spawned.
|
|
83
|
-
*/
|
|
84
|
-
export declare function readPpidCommand(ppid: number): string;
|
|
85
|
-
/**
|
|
86
|
-
* Return the actual controlling tty path (e.g. `/dev/ttys001`) when one
|
|
87
|
-
* exists, or the literal string `not-a-tty` otherwise. Bash-core parity
|
|
88
|
-
* (push-review-core.sh §426): `tty 2>/dev/null || echo "not-a-tty"`.
|
|
89
|
-
*
|
|
90
|
-
* Codex pass-1 on phase 1 flagged the earlier `/dev/tty` literal as a
|
|
91
|
-
* parity regression — the audit consumer expects the real device path so
|
|
92
|
-
* forensic tooling can distinguish tty1 from tty2 on the same host.
|
|
93
|
-
*
|
|
94
|
-
* Implementation: we shell out to `tty(1)` exactly as bash does. On
|
|
95
|
-
* systems without `tty` (distroless, minimal Alpine without coreutils-
|
|
96
|
-
* full) we degrade to `not-a-tty`.
|
|
97
|
-
*/
|
|
98
|
-
export declare function readTty(): string;
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OS / agent / repo identity collection for audit records.
|
|
3
|
-
*
|
|
4
|
-
* Closes defect M (0.9.4): the bash core's `jq --arg os_pid "$PID"` wrote
|
|
5
|
-
* the pid as a JSON string; the audit consumer expected an integer, and the
|
|
6
|
-
* schema had to tolerate both. The TS port emits pid/ppid as numbers from
|
|
7
|
-
* day one (the JS runtime's `process.pid` is already a `number`), removing
|
|
8
|
-
* the whole class of jq `--arg` vs `--argjson` confusion.
|
|
9
|
-
*
|
|
10
|
-
* ## Why capture this at all
|
|
11
|
-
*
|
|
12
|
-
* A skip-audit record (`REA_SKIP_PUSH_REVIEW` / `REA_SKIP_CODEX_REVIEW`) is
|
|
13
|
-
* the one place the gate is voluntarily weakened. The actor field (from
|
|
14
|
-
* `git config user.email`) is mutable — a process with write access to
|
|
15
|
-
* `.git/config` can stamp any email it likes. Supplementing that with
|
|
16
|
-
* non-forgeable host-level identity (uid, hostname, pid, ppid, tty, CI
|
|
17
|
-
* flag) gives a forensic investigator something to cross-reference when
|
|
18
|
-
* a skip record turns out to have been unauthorized.
|
|
19
|
-
*
|
|
20
|
-
* ## Determinism + testability
|
|
21
|
-
*
|
|
22
|
-
* Every collector is a plain function over `process` / `os` / `node:child_process`
|
|
23
|
-
* so tests can stub the collector's inputs cleanly. The public API takes no
|
|
24
|
-
* arguments (production use) and the helpers are exported for tests.
|
|
25
|
-
*/
|
|
26
|
-
import { hostname, userInfo } from 'node:os';
|
|
27
|
-
import { spawnSync } from 'node:child_process';
|
|
28
|
-
/**
|
|
29
|
-
* Collect current-process OS identity. Every individual collector degrades
|
|
30
|
-
* to an empty-string fallback on any exception — matching the bash core's
|
|
31
|
-
* `id -u || echo ""`, `whoami || echo ""`, `hostname || echo ""` pattern
|
|
32
|
-
* (push-review-core.sh §420-422, 426). Codex pass-1 on phase 1 flagged
|
|
33
|
-
* the earlier implementation that called `userInfo()` and `hostname()`
|
|
34
|
-
* outside any try/catch and could therefore throw on hosts with broken
|
|
35
|
-
* NSS/passwd lookups, which would silently block the skip-audit path.
|
|
36
|
-
*/
|
|
37
|
-
export function collectOsIdentity() {
|
|
38
|
-
const { uid, whoami } = readUidAndWhoami();
|
|
39
|
-
const host = readHostname();
|
|
40
|
-
const pid = process.pid;
|
|
41
|
-
const ppid = process.ppid;
|
|
42
|
-
const ppid_cmd = readPpidCommand(ppid);
|
|
43
|
-
const tty = readTty();
|
|
44
|
-
const ci = process.env['CI'] ?? '';
|
|
45
|
-
return { uid, whoami, hostname: host, pid, ppid, ppid_cmd, tty, ci };
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Read POSIX uid + whoami. Each field is collected INDEPENDENTLY so a
|
|
49
|
-
* partial-lookup-failure (e.g. LDAP/NSS returns the uid but no passwd
|
|
50
|
-
* entry) still yields one field rather than dropping both. Bash-core
|
|
51
|
-
* parity (push-review-core.sh §420-421): `id -u` and `whoami` are two
|
|
52
|
-
* separate invocations, each with its own `|| echo ""` fallback.
|
|
53
|
-
*
|
|
54
|
-
* Codex pass-2 on phase 1 flagged the prior single-`userInfo()` version:
|
|
55
|
-
* a broken NSS lookup zeroed out both fields at once, weakening forensic
|
|
56
|
-
* metadata on shared hosts. We now prefer the POSIX primitives
|
|
57
|
-
* (`os.userInfo()` internally reads the passwd entry) but isolate the
|
|
58
|
-
* failures so uid and whoami cannot both disappear together when only
|
|
59
|
-
* one of them is actually unavailable.
|
|
60
|
-
*/
|
|
61
|
-
export function readUidAndWhoami() {
|
|
62
|
-
let uid = '';
|
|
63
|
-
let whoami = '';
|
|
64
|
-
try {
|
|
65
|
-
// Node exposes the raw numeric uid via process.getuid() on POSIX —
|
|
66
|
-
// this goes through the kernel, not through passwd. If it throws
|
|
67
|
-
// (Windows), the fallback is '' and we still try whoami below.
|
|
68
|
-
const getuid = process.getuid;
|
|
69
|
-
if (typeof getuid === 'function') {
|
|
70
|
-
const raw = getuid.call(process);
|
|
71
|
-
if (typeof raw === 'number' && raw >= 0)
|
|
72
|
-
uid = String(raw);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
// swallow — uid stays ''
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
const info = userInfo({ encoding: 'utf8' });
|
|
80
|
-
whoami = info.username ?? '';
|
|
81
|
-
// If the kernel-uid probe above failed but userInfo() succeeded, use
|
|
82
|
-
// its uid as a secondary source.
|
|
83
|
-
if (uid.length === 0 && typeof info.uid === 'number' && info.uid >= 0) {
|
|
84
|
-
uid = String(info.uid);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
// swallow — whoami stays ''
|
|
89
|
-
}
|
|
90
|
-
return { uid, whoami };
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Read `hostname` via `os.hostname`. Returns an empty string on any error.
|
|
94
|
-
* Exported for unit tests.
|
|
95
|
-
*/
|
|
96
|
-
export function readHostname() {
|
|
97
|
-
try {
|
|
98
|
-
return hostname();
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return '';
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Read `ps -o command= -p <ppid>` safely. Returns the (truncated) command
|
|
106
|
-
* or an empty string on any failure.
|
|
107
|
-
*
|
|
108
|
-
* Security: args are passed as an array, never interpolated into a shell
|
|
109
|
-
* string. `ps` is the only executable spawned.
|
|
110
|
-
*/
|
|
111
|
-
export function readPpidCommand(ppid) {
|
|
112
|
-
if (!Number.isFinite(ppid) || ppid <= 0)
|
|
113
|
-
return '';
|
|
114
|
-
try {
|
|
115
|
-
const result = spawnSync('ps', ['-o', 'command=', '-p', String(ppid)], {
|
|
116
|
-
encoding: 'utf8',
|
|
117
|
-
timeout: 2_000,
|
|
118
|
-
});
|
|
119
|
-
if (result.status !== 0)
|
|
120
|
-
return '';
|
|
121
|
-
const out = (result.stdout ?? '').replace(/\n+$/, '');
|
|
122
|
-
return out.slice(0, 512);
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
return '';
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Return the actual controlling tty path (e.g. `/dev/ttys001`) when one
|
|
130
|
-
* exists, or the literal string `not-a-tty` otherwise. Bash-core parity
|
|
131
|
-
* (push-review-core.sh §426): `tty 2>/dev/null || echo "not-a-tty"`.
|
|
132
|
-
*
|
|
133
|
-
* Codex pass-1 on phase 1 flagged the earlier `/dev/tty` literal as a
|
|
134
|
-
* parity regression — the audit consumer expects the real device path so
|
|
135
|
-
* forensic tooling can distinguish tty1 from tty2 on the same host.
|
|
136
|
-
*
|
|
137
|
-
* Implementation: we shell out to `tty(1)` exactly as bash does. On
|
|
138
|
-
* systems without `tty` (distroless, minimal Alpine without coreutils-
|
|
139
|
-
* full) we degrade to `not-a-tty`.
|
|
140
|
-
*/
|
|
141
|
-
export function readTty() {
|
|
142
|
-
try {
|
|
143
|
-
const result = spawnSync('tty', [], {
|
|
144
|
-
encoding: 'utf8',
|
|
145
|
-
timeout: 2_000,
|
|
146
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
147
|
-
});
|
|
148
|
-
if (result.status === 0) {
|
|
149
|
-
const out = (result.stdout ?? '').replace(/\n+$/, '');
|
|
150
|
-
if (out.length > 0)
|
|
151
|
-
return out;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
// fall through to the not-a-tty fallback
|
|
156
|
-
}
|
|
157
|
-
return 'not-a-tty';
|
|
158
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Policy accessors for the review-gate modules.
|
|
3
|
-
*
|
|
4
|
-
* The bash core resolves `review.codex_required` via `node dist/scripts/read-
|
|
5
|
-
* policy-field.js` and evaluates `REA_SKIP_PUSH_REVIEW` / `REA_SKIP_CODEX_REVIEW`
|
|
6
|
-
* directly from env vars. The TS port composes the same behavior over the
|
|
7
|
-
* already-TS `loadPolicy` helper, removing the fork/exec hop and the
|
|
8
|
-
* exit-code-parsing that came with it.
|
|
9
|
-
*
|
|
10
|
-
* Fail-closed: when the policy file is malformed or unreadable, the
|
|
11
|
-
* returned `codex_required` is `true`. This matches the bash core's
|
|
12
|
-
* "treating as true" path (design §2, security carry-forward).
|
|
13
|
-
*/
|
|
14
|
-
import type { Policy } from '../../policy/types.js';
|
|
15
|
-
export interface ResolvedPolicy {
|
|
16
|
-
/** Resolved `review.codex_required`; true when malformed/absent. */
|
|
17
|
-
codex_required: boolean;
|
|
18
|
-
/** Resolved `review.allow_skip_in_ci`; false when absent. */
|
|
19
|
-
allow_skip_in_ci: boolean;
|
|
20
|
-
/** Full policy (undefined when load failed — caller emits a WARN). */
|
|
21
|
-
policy: Policy | null;
|
|
22
|
-
/** Warning text from the loader, if any — surfaced to stderr by the caller. */
|
|
23
|
-
warning: string | null;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Resolve review-related policy fields. Never throws — any error path
|
|
27
|
-
* returns `codex_required: true` with a `warning` populated so the caller
|
|
28
|
-
* can decide whether to echo it.
|
|
29
|
-
*/
|
|
30
|
-
export declare function resolveReviewPolicy(baseDir: string): ResolvedPolicy;
|
|
31
|
-
/**
|
|
32
|
-
* Skip-env evaluation. The bash core reads `REA_SKIP_PUSH_REVIEW` +
|
|
33
|
-
* `REA_SKIP_CODEX_REVIEW` with simple non-empty semantics. We preserve
|
|
34
|
-
* that exactly: any non-empty value triggers the skip path, and the
|
|
35
|
-
* VALUE is used as the skip reason. Empty or unset = no skip.
|
|
36
|
-
*/
|
|
37
|
-
export interface SkipEnv {
|
|
38
|
-
/** `REA_SKIP_PUSH_REVIEW` value or null. */
|
|
39
|
-
push_review_reason: string | null;
|
|
40
|
-
/** `REA_SKIP_CODEX_REVIEW` value or null. */
|
|
41
|
-
codex_review_reason: string | null;
|
|
42
|
-
}
|
|
43
|
-
export declare function readSkipEnv(env?: NodeJS.ProcessEnv): SkipEnv;
|
|
44
|
-
/**
|
|
45
|
-
* True iff `CI` env is set to a non-empty value. The bash core checks
|
|
46
|
-
* `[[ -n "${CI:-}" ]]` — we match that.
|
|
47
|
-
*/
|
|
48
|
-
export declare function isCiContext(env?: NodeJS.ProcessEnv): boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Legacy-bash kill switch (design §11.2). When `REA_LEGACY_PUSH_REVIEW=1`,
|
|
51
|
-
* the CLI entry point delegates to the preserved bash core for one
|
|
52
|
-
* release window. Advertised in `rea doctor`; sunset after 90 days of
|
|
53
|
-
* clean 0.11.x running on canaries.
|
|
54
|
-
*/
|
|
55
|
-
export declare function isLegacyBashKillSwitchOn(env?: NodeJS.ProcessEnv): boolean;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Policy accessors for the review-gate modules.
|
|
3
|
-
*
|
|
4
|
-
* The bash core resolves `review.codex_required` via `node dist/scripts/read-
|
|
5
|
-
* policy-field.js` and evaluates `REA_SKIP_PUSH_REVIEW` / `REA_SKIP_CODEX_REVIEW`
|
|
6
|
-
* directly from env vars. The TS port composes the same behavior over the
|
|
7
|
-
* already-TS `loadPolicy` helper, removing the fork/exec hop and the
|
|
8
|
-
* exit-code-parsing that came with it.
|
|
9
|
-
*
|
|
10
|
-
* Fail-closed: when the policy file is malformed or unreadable, the
|
|
11
|
-
* returned `codex_required` is `true`. This matches the bash core's
|
|
12
|
-
* "treating as true" path (design §2, security carry-forward).
|
|
13
|
-
*/
|
|
14
|
-
import { loadPolicy } from '../../policy/loader.js';
|
|
15
|
-
/**
|
|
16
|
-
* Resolve review-related policy fields. Never throws — any error path
|
|
17
|
-
* returns `codex_required: true` with a `warning` populated so the caller
|
|
18
|
-
* can decide whether to echo it.
|
|
19
|
-
*/
|
|
20
|
-
export function resolveReviewPolicy(baseDir) {
|
|
21
|
-
try {
|
|
22
|
-
const policy = loadPolicy(baseDir);
|
|
23
|
-
const review = policy.review;
|
|
24
|
-
const codex_required = review?.codex_required === false ? false : true;
|
|
25
|
-
const allow_skip_in_ci = review?.allow_skip_in_ci === true;
|
|
26
|
-
return {
|
|
27
|
-
codex_required,
|
|
28
|
-
allow_skip_in_ci,
|
|
29
|
-
policy,
|
|
30
|
-
warning: null,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
catch (err) {
|
|
34
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
35
|
-
// Policy-file-not-found is a first-run condition for consumers running
|
|
36
|
-
// the gate before `rea init`; treat codex_required as true and surface
|
|
37
|
-
// the warning so the operator knows what state the gate is in.
|
|
38
|
-
return {
|
|
39
|
-
codex_required: true,
|
|
40
|
-
allow_skip_in_ci: false,
|
|
41
|
-
policy: null,
|
|
42
|
-
warning: `review-gate: could not load .rea/policy.yaml — ${msg}. Treating codex_required=true (fail-closed).`,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
export function readSkipEnv(env = process.env) {
|
|
47
|
-
const pr = env['REA_SKIP_PUSH_REVIEW'];
|
|
48
|
-
const cr = env['REA_SKIP_CODEX_REVIEW'];
|
|
49
|
-
return {
|
|
50
|
-
push_review_reason: typeof pr === 'string' && pr.length > 0 ? pr : null,
|
|
51
|
-
codex_review_reason: typeof cr === 'string' && cr.length > 0 ? cr : null,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* True iff `CI` env is set to a non-empty value. The bash core checks
|
|
56
|
-
* `[[ -n "${CI:-}" ]]` — we match that.
|
|
57
|
-
*/
|
|
58
|
-
export function isCiContext(env = process.env) {
|
|
59
|
-
const v = env['CI'];
|
|
60
|
-
return typeof v === 'string' && v.length > 0;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Legacy-bash kill switch (design §11.2). When `REA_LEGACY_PUSH_REVIEW=1`,
|
|
64
|
-
* the CLI entry point delegates to the preserved bash core for one
|
|
65
|
-
* release window. Advertised in `rea doctor`; sunset after 90 days of
|
|
66
|
-
* clean 0.11.x running on canaries.
|
|
67
|
-
*/
|
|
68
|
-
export function isLegacyBashKillSwitchOn(env = process.env) {
|
|
69
|
-
const v = env['REA_LEGACY_PUSH_REVIEW'];
|
|
70
|
-
return typeof v === 'string' && v.length > 0 && v !== '0';
|
|
71
|
-
}
|