@coderifts/agent-guard 1.3.0 → 1.5.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/cjs/deploy-gate.d.ts +103 -0
- package/dist/cjs/deploy-gate.d.ts.map +1 -0
- package/dist/cjs/deploy-gate.js +159 -0
- package/dist/cjs/deploy-gate.js.map +1 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/merge-gate.d.ts +87 -0
- package/dist/cjs/merge-gate.d.ts.map +1 -0
- package/dist/cjs/merge-gate.js +133 -0
- package/dist/cjs/merge-gate.js.map +1 -0
- package/dist/esm/deploy-gate.d.ts +103 -0
- package/dist/esm/deploy-gate.d.ts.map +1 -0
- package/dist/esm/deploy-gate.js +156 -0
- package/dist/esm/deploy-gate.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/merge-gate.d.ts +87 -0
- package/dist/esm/merge-gate.d.ts.map +1 -0
- package/dist/esm/merge-gate.js +130 -0
- package/dist/esm/merge-gate.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deploy-gate (#8) — the PURE deploy authorization decision (deploy-gate-SPEC §1). The DEPLOY sibling
|
|
3
|
+
* of the merge gate: it answers "may THIS artifact deploy to THIS environment, given the finished
|
|
4
|
+
* receipt and the observed pipeline-enforcement state?"
|
|
5
|
+
*
|
|
6
|
+
* Same shape as the merge gate, different operation. It is a PURE function — NO I/O, NO CD-API call
|
|
7
|
+
* (pipeline enforcement is an INPUT, observed by the host and passed in). It does NOT re-decide: it
|
|
8
|
+
* reads only the finished receipt fields — the deterministic verdict stays the server's (D7). Same
|
|
9
|
+
* inputs → same output (D9).
|
|
10
|
+
*
|
|
11
|
+
* The three deploy-specific binds are the whole point: T7 (a MERGE — or operation-less — receipt can
|
|
12
|
+
* never deploy), environment binding (a staging ALLOW never authorizes production), and artifact
|
|
13
|
+
* binding (an old artifact's ALLOW never deploys a newer one). Scope honesty mirrors the merge gate:
|
|
14
|
+
* `inescapable_deploy` is true ONLY in the fully-enforcing, non-bypassable case; otherwise the check
|
|
15
|
+
* may be `success` for visibility but the claim is false with the residual named.
|
|
16
|
+
*
|
|
17
|
+
* Public source (ships in the npm package): references only receipt/target/enforcement fields and
|
|
18
|
+
* generic deploy-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or
|
|
19
|
+
* secrets.
|
|
20
|
+
*/
|
|
21
|
+
import type { GateStatusState, EnforcementState } from './merge-gate.js';
|
|
22
|
+
export type { GateStatusState, EnforcementState } from './merge-gate.js';
|
|
23
|
+
/** The 13 deploy-gate reason codes (§1.1). Success uses allow_current_deploy; enforcement_not_configured
|
|
24
|
+
* and bypass_open name the honesty residual on an otherwise-green check. */
|
|
25
|
+
export type DeployGateReason = 'allow_current_deploy' | 'no_receipt' | 'receipt_not_authorized' | 'operation_mismatch' | 'env_mismatch' | 'stale_artifact' | 'fingerprint_mismatch' | 'body_hash_mismatch' | 'target_mismatch' | 'decision_not_allow' | 'inputs_incomplete' | 'enforcement_not_configured' | 'bypass_open';
|
|
26
|
+
/** What is being deployed — all fields are INPUTS (the pure function performs no discovery). */
|
|
27
|
+
export type DeployTarget = {
|
|
28
|
+
environment: string;
|
|
29
|
+
/** Immutable artifact identity: content digest, or a commit SHA when that is the deploy unit. */
|
|
30
|
+
artifact_id: string;
|
|
31
|
+
service?: string;
|
|
32
|
+
pipeline_run_id?: string;
|
|
33
|
+
};
|
|
34
|
+
/** A view of the finished receipt. Reuses the shipped field names + the deploy analogs of bound_head_sha. */
|
|
35
|
+
export type DeployReceiptView = {
|
|
36
|
+
currently_authorized: boolean;
|
|
37
|
+
authz_reason?: string;
|
|
38
|
+
decision: 'ALLOW' | 'WARN' | 'REQUIRE_APPROVAL' | 'BLOCK' | string;
|
|
39
|
+
execution_action?: 'CONTINUE' | 'CONTINUE_WITH_MONITORING' | 'REQUEST_APPROVAL' | 'STOP' | string;
|
|
40
|
+
/** MUST be 'deploy' for success (T7). */
|
|
41
|
+
operation?: 'merge' | 'deploy' | 'tool_call' | 'publish' | string;
|
|
42
|
+
/** The environment this receipt authorizes (deploy analog of bound_head_sha). */
|
|
43
|
+
bound_environment?: string | null;
|
|
44
|
+
/** The artifact/commit this receipt was issued for (deploy analog of bound_head_sha). */
|
|
45
|
+
bound_artifact_id?: string | null;
|
|
46
|
+
verdict_fingerprint?: string;
|
|
47
|
+
body_hash?: string;
|
|
48
|
+
target_id?: string;
|
|
49
|
+
signature_valid?: boolean;
|
|
50
|
+
};
|
|
51
|
+
/** Pipeline-enforcement observation (from the host's CD-config read — the pure gate consumes it). */
|
|
52
|
+
export type DeployEnforcementState = {
|
|
53
|
+
enforcement: EnforcementState;
|
|
54
|
+
bypass_possible: boolean;
|
|
55
|
+
required_step_name?: string;
|
|
56
|
+
};
|
|
57
|
+
export type DeployRequiredContext = {
|
|
58
|
+
operation?: 'deploy' | string;
|
|
59
|
+
repository?: string;
|
|
60
|
+
service?: string;
|
|
61
|
+
expected_fingerprint?: string;
|
|
62
|
+
expected_body_hash?: string;
|
|
63
|
+
enforcement: DeployEnforcementState;
|
|
64
|
+
/** Default true: a missing bound_environment on the receipt → env_mismatch. */
|
|
65
|
+
require_bound_environment?: boolean;
|
|
66
|
+
/** Default true: a missing bound_artifact_id → stale_artifact. */
|
|
67
|
+
require_bound_artifact?: boolean;
|
|
68
|
+
allowWarnDeploy?: boolean;
|
|
69
|
+
allowPrefixCompare?: boolean;
|
|
70
|
+
allowPending?: boolean;
|
|
71
|
+
};
|
|
72
|
+
export type DeployGateInput = {
|
|
73
|
+
deployTarget: DeployTarget;
|
|
74
|
+
receipt: DeployReceiptView | null;
|
|
75
|
+
requiredContext: DeployRequiredContext;
|
|
76
|
+
/** Incomplete/no-receipt → pending when true, else failure (default false). */
|
|
77
|
+
allowPending?: boolean;
|
|
78
|
+
/** Green the gate on WARN as well as ALLOW (default false; stricter for prod-shaped envs). */
|
|
79
|
+
allowWarnDeploy?: boolean;
|
|
80
|
+
/** Compare env/artifact by prefix instead of full equality (default false). */
|
|
81
|
+
allowPrefixCompare?: boolean;
|
|
82
|
+
};
|
|
83
|
+
export type DeployGateDecision = {
|
|
84
|
+
deploy_allowed: boolean;
|
|
85
|
+
state: GateStatusState;
|
|
86
|
+
reason: DeployGateReason;
|
|
87
|
+
enforcement_state: EnforcementState;
|
|
88
|
+
inescapable_deploy: boolean;
|
|
89
|
+
/** Named residual when the check is green but NOT inescapable (honesty channel, §3.2). */
|
|
90
|
+
residual?: DeployGateReason;
|
|
91
|
+
detail: {
|
|
92
|
+
environment: string;
|
|
93
|
+
artifact_id: string;
|
|
94
|
+
bound_environment: string | null;
|
|
95
|
+
bound_artifact_id: string | null;
|
|
96
|
+
operation: string | null;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* §1.4 — the normative, first-match-wins deploy decision. Pure; no I/O; deterministic.
|
|
101
|
+
*/
|
|
102
|
+
export declare function deployGate(input: DeployGateInput): DeployGateDecision;
|
|
103
|
+
//# sourceMappingURL=deploy-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-gate.d.ts","sourceRoot":"","sources":["../../src/deploy-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* deploy-gate (#8) — the PURE deploy authorization decision (deploy-gate-SPEC §1). The DEPLOY sibling
|
|
4
|
+
* of the merge gate: it answers "may THIS artifact deploy to THIS environment, given the finished
|
|
5
|
+
* receipt and the observed pipeline-enforcement state?"
|
|
6
|
+
*
|
|
7
|
+
* Same shape as the merge gate, different operation. It is a PURE function — NO I/O, NO CD-API call
|
|
8
|
+
* (pipeline enforcement is an INPUT, observed by the host and passed in). It does NOT re-decide: it
|
|
9
|
+
* reads only the finished receipt fields — the deterministic verdict stays the server's (D7). Same
|
|
10
|
+
* inputs → same output (D9).
|
|
11
|
+
*
|
|
12
|
+
* The three deploy-specific binds are the whole point: T7 (a MERGE — or operation-less — receipt can
|
|
13
|
+
* never deploy), environment binding (a staging ALLOW never authorizes production), and artifact
|
|
14
|
+
* binding (an old artifact's ALLOW never deploys a newer one). Scope honesty mirrors the merge gate:
|
|
15
|
+
* `inescapable_deploy` is true ONLY in the fully-enforcing, non-bypassable case; otherwise the check
|
|
16
|
+
* may be `success` for visibility but the claim is false with the residual named.
|
|
17
|
+
*
|
|
18
|
+
* Public source (ships in the npm package): references only receipt/target/enforcement fields and
|
|
19
|
+
* generic deploy-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or
|
|
20
|
+
* secrets.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.deployGate = deployGate;
|
|
24
|
+
// ── helpers (pure) ────────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
function norm(s) {
|
|
26
|
+
return String(s == null ? '' : s).trim().toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
/** §1.2 — full lowercase equality by default (env exact; artifact digest/sha); prefix opt-in. */
|
|
29
|
+
function sameNorm(a, b, allowPrefix) {
|
|
30
|
+
const na = norm(a);
|
|
31
|
+
const nb = norm(b);
|
|
32
|
+
if (!na || !nb)
|
|
33
|
+
return false;
|
|
34
|
+
if (na === nb)
|
|
35
|
+
return true;
|
|
36
|
+
if (allowPrefix && na.length >= 7 && nb.length >= 7 && (na.startsWith(nb) || nb.startsWith(na)))
|
|
37
|
+
return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
/** §1.3 — allow-class for deploy: ALLOW (or WARN only when opted in) + a continue-class action. */
|
|
41
|
+
function isAllowClass(receipt, allowWarnDeploy) {
|
|
42
|
+
const dec = receipt.decision;
|
|
43
|
+
const decisionOk = dec === 'ALLOW' || (dec === 'WARN' && allowWarnDeploy === true);
|
|
44
|
+
if (!decisionOk)
|
|
45
|
+
return false;
|
|
46
|
+
const ea = receipt.execution_action;
|
|
47
|
+
if (ea === undefined || ea === null || ea === '')
|
|
48
|
+
return true;
|
|
49
|
+
return ea === 'CONTINUE' || ea === 'CONTINUE_WITH_MONITORING';
|
|
50
|
+
}
|
|
51
|
+
/** target_id must name this service/repository (exact or `svc:`/`repo:`/path/colon suffix). */
|
|
52
|
+
function idMatchesName(targetId, name) {
|
|
53
|
+
const t = norm(targetId);
|
|
54
|
+
const r = norm(name);
|
|
55
|
+
return t === r || t === `svc:${r}` || t === `repo:${r}` || t.endsWith(`:${r}`) || t.endsWith(`/${r}`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* §1.4 — the normative, first-match-wins deploy decision. Pure; no I/O; deterministic.
|
|
59
|
+
*/
|
|
60
|
+
function deployGate(input) {
|
|
61
|
+
const target = input.deployTarget || {};
|
|
62
|
+
const rc = input.requiredContext || {};
|
|
63
|
+
const enf = rc.enforcement || { enforcement: 'UNKNOWN', bypass_possible: true };
|
|
64
|
+
const enforcement_state = enf.enforcement;
|
|
65
|
+
const opRequired = rc.operation ?? 'deploy';
|
|
66
|
+
const requireEnv = rc.require_bound_environment !== false; // default true
|
|
67
|
+
const requireArt = rc.require_bound_artifact !== false; // default true
|
|
68
|
+
const allowPending = input.allowPending ?? rc.allowPending ?? false;
|
|
69
|
+
const allowWarnDeploy = input.allowWarnDeploy ?? rc.allowWarnDeploy ?? false;
|
|
70
|
+
const allowPrefix = input.allowPrefixCompare ?? rc.allowPrefixCompare ?? false;
|
|
71
|
+
const receipt = input.receipt;
|
|
72
|
+
const detail = {
|
|
73
|
+
environment: norm(target.environment),
|
|
74
|
+
artifact_id: norm(target.artifact_id),
|
|
75
|
+
bound_environment: receipt && receipt.bound_environment != null ? norm(receipt.bound_environment) : null,
|
|
76
|
+
bound_artifact_id: receipt && receipt.bound_artifact_id != null ? norm(receipt.bound_artifact_id) : null,
|
|
77
|
+
operation: receipt && receipt.operation != null ? String(receipt.operation) : null,
|
|
78
|
+
};
|
|
79
|
+
const deny = (state, reason) => ({
|
|
80
|
+
deploy_allowed: false, state, reason, enforcement_state, inescapable_deploy: false, detail,
|
|
81
|
+
});
|
|
82
|
+
// 0) incomplete target → pending/failure (fail-closed on missing evaluation input).
|
|
83
|
+
if (!target.environment || String(target.environment).trim() === ''
|
|
84
|
+
|| !target.artifact_id || String(target.artifact_id).trim() === '') {
|
|
85
|
+
return deny(allowPending ? 'pending' : 'failure', 'inputs_incomplete');
|
|
86
|
+
}
|
|
87
|
+
// 1) no receipt → fail-closed (D4).
|
|
88
|
+
if (receipt === null || receipt === undefined) {
|
|
89
|
+
return deny(allowPending ? 'pending' : 'failure', 'no_receipt');
|
|
90
|
+
}
|
|
91
|
+
// 2) lifecycle authorization — a valid signature alone is NOT sufficient (D5).
|
|
92
|
+
if (receipt.currently_authorized !== true) {
|
|
93
|
+
return deny('failure', 'receipt_not_authorized');
|
|
94
|
+
}
|
|
95
|
+
// 3) T7 — operation MUST be deploy. A merge (or any other) receipt cannot deploy, and a MISSING
|
|
96
|
+
// operation also fails closed (stricter than the merge gate — deploy is higher-risk) (D1).
|
|
97
|
+
if (receipt.operation == null || norm(receipt.operation) !== norm(opRequired)) {
|
|
98
|
+
return deny('failure', 'operation_mismatch');
|
|
99
|
+
}
|
|
100
|
+
// 4) ENVIRONMENT binding — a staging ALLOW never authorizes production (D2).
|
|
101
|
+
if (requireEnv) {
|
|
102
|
+
if (!receipt.bound_environment || !sameNorm(receipt.bound_environment, target.environment, allowPrefix)) {
|
|
103
|
+
return deny('failure', 'env_mismatch');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (receipt.bound_environment && !sameNorm(receipt.bound_environment, target.environment, allowPrefix)) {
|
|
107
|
+
return deny('failure', 'env_mismatch');
|
|
108
|
+
}
|
|
109
|
+
// 5) ARTIFACT binding — an old artifact's ALLOW never deploys a newer one (D3, the stale analog).
|
|
110
|
+
if (requireArt) {
|
|
111
|
+
if (!receipt.bound_artifact_id || !sameNorm(receipt.bound_artifact_id, target.artifact_id, allowPrefix)) {
|
|
112
|
+
return deny('failure', 'stale_artifact');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (receipt.bound_artifact_id && !sameNorm(receipt.bound_artifact_id, target.artifact_id, allowPrefix)) {
|
|
116
|
+
return deny('failure', 'stale_artifact');
|
|
117
|
+
}
|
|
118
|
+
// 6) optional re-bind to the current change set.
|
|
119
|
+
if (rc.expected_fingerprint != null && receipt.verdict_fingerprint !== rc.expected_fingerprint) {
|
|
120
|
+
return deny('failure', 'fingerprint_mismatch');
|
|
121
|
+
}
|
|
122
|
+
if (rc.expected_body_hash != null && receipt.body_hash !== rc.expected_body_hash) {
|
|
123
|
+
return deny('failure', 'body_hash_mismatch');
|
|
124
|
+
}
|
|
125
|
+
// 7) optional service / repository target binding.
|
|
126
|
+
if (rc.service && receipt.target_id) {
|
|
127
|
+
if (!idMatchesName(receipt.target_id, rc.service))
|
|
128
|
+
return deny('failure', 'target_mismatch');
|
|
129
|
+
}
|
|
130
|
+
else if (rc.repository && receipt.target_id) {
|
|
131
|
+
if (!idMatchesName(receipt.target_id, rc.repository))
|
|
132
|
+
return deny('failure', 'target_mismatch');
|
|
133
|
+
}
|
|
134
|
+
// 8) decision class — only an allow-class verdict greens the deploy gate (D6).
|
|
135
|
+
if (!isAllowClass(receipt, allowWarnDeploy)) {
|
|
136
|
+
return deny('failure', 'decision_not_allow');
|
|
137
|
+
}
|
|
138
|
+
// 9) success for the CHECK / pipeline step — green for visibility regardless of enforcement strength.
|
|
139
|
+
// 10) inescapable_deploy claim (STRICT, §1.4 step 10 / D8): only when the pipeline actually enforces
|
|
140
|
+
// the step AND no bypass is possible. Never true otherwise.
|
|
141
|
+
const inescapable_deploy = enforcement_state === 'ENFORCING' && enf.bypass_possible === false;
|
|
142
|
+
let residual;
|
|
143
|
+
if (!inescapable_deploy) {
|
|
144
|
+
if (enforcement_state === 'ENFORCING')
|
|
145
|
+
residual = 'bypass_open'; // enforced, but a bypass exists
|
|
146
|
+
else
|
|
147
|
+
residual = 'enforcement_not_configured'; // ADVISORY / ABSENT / UNKNOWN
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
deploy_allowed: true,
|
|
151
|
+
state: 'success',
|
|
152
|
+
reason: 'allow_current_deploy',
|
|
153
|
+
enforcement_state,
|
|
154
|
+
inescapable_deploy,
|
|
155
|
+
...(residual ? { residual } : {}),
|
|
156
|
+
detail,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=deploy-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-gate.js","sourceRoot":"","sources":["../../src/deploy-gate.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -32,4 +32,8 @@ export type { ResolveInput, ResolveConfig, ResolveResult, ResolvedArtifact, Reso
|
|
|
32
32
|
export { matchGlob, globToRegExp } from './resolver-glob.js';
|
|
33
33
|
export { guardToolRegistry, RegistryConstructionError } from './tool-registry.js';
|
|
34
34
|
export type { RawTool, ProtectedTool, ToolMutationClass, EnforcementCoverage, RegistryCoverageReport, GuardToolRegistryConfig, RegistryResult, ToolBinder, RegistryConstructionErrorCode, } from './tool-registry.js';
|
|
35
|
+
export { gateDecision } from './merge-gate.js';
|
|
36
|
+
export type { GateDecisionInput, GateDecision, ReceiptView, RequiredContext, ProtectionState, GateReason, GateStatusState, EnforcementState, } from './merge-gate.js';
|
|
37
|
+
export { deployGate } from './deploy-gate.js';
|
|
38
|
+
export type { DeployGateInput, DeployGateDecision, DeployReceiptView, DeployRequiredContext, DeployEnforcementState, DeployTarget, DeployGateReason, } from './deploy-gate.js';
|
|
35
39
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAGxG,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC1G,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EACnF,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAC7E,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,GAC3G,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,uBAAuB,EACvB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,UAAU,EACV,SAAS,EACT,sBAAsB,EACtB,eAAe,EACf,QAAQ,GACT,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EACV,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAC7E,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,IAAI,oBAAoB,GAClG,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,cAAc,EACd,UAAU,EACV,6BAA6B,GAC9B,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAGxG,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC1G,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EACnF,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAC7E,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,GAC3G,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,uBAAuB,EACvB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,UAAU,EACV,SAAS,EACT,sBAAsB,EACtB,eAAe,EACf,QAAQ,GACT,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EACV,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAC7E,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,IAAI,oBAAoB,GAClG,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,cAAc,EACd,UAAU,EACV,6BAA6B,GAC9B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EACV,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAC9E,UAAU,EAAE,eAAe,EAAE,gBAAgB,GAC9C,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,qBAAqB,EAC7E,sBAAsB,EAAE,YAAY,EAAE,gBAAgB,GACvD,MAAM,kBAAkB,CAAC"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.RegistryConstructionError = exports.guardToolRegistry = exports.globToRegExp = exports.matchGlob = exports.resolveArtifacts = exports.deriveKeySignal = exports.pathClass = exports.classifyCommand = exports.projectState = exports.emptySessionState = exports.computeTainted = exports.evaluate = exports.updateSession = exports.SESSION_TAINT_VERSION = exports.SessionTaintTracker = exports.readDecision = exports.computeBundleFingerprint = exports.computeArtifactDigest = exports.evaluateEnvelope = exports.canonicalJson = exports.computeBodyHash = exports.bindReceiptToEnvelope = exports.DETECTOR_VERSION = exports.builtinDetector = exports.guardToolCall = void 0;
|
|
22
|
+
exports.deployGate = exports.gateDecision = exports.RegistryConstructionError = exports.guardToolRegistry = exports.globToRegExp = exports.matchGlob = exports.resolveArtifacts = exports.deriveKeySignal = exports.pathClass = exports.classifyCommand = exports.projectState = exports.emptySessionState = exports.computeTainted = exports.evaluate = exports.updateSession = exports.SESSION_TAINT_VERSION = exports.SessionTaintTracker = exports.readDecision = exports.computeBundleFingerprint = exports.computeArtifactDigest = exports.evaluateEnvelope = exports.canonicalJson = exports.computeBodyHash = exports.bindReceiptToEnvelope = exports.DETECTOR_VERSION = exports.builtinDetector = exports.guardToolCall = void 0;
|
|
23
23
|
var guard_js_1 = require("./guard.js");
|
|
24
24
|
Object.defineProperty(exports, "guardToolCall", { enumerable: true, get: function () { return guard_js_1.guardToolCall; } });
|
|
25
25
|
var detector_js_1 = require("./detector.js");
|
|
@@ -61,4 +61,10 @@ Object.defineProperty(exports, "globToRegExp", { enumerable: true, get: function
|
|
|
61
61
|
var tool_registry_js_1 = require("./tool-registry.js");
|
|
62
62
|
Object.defineProperty(exports, "guardToolRegistry", { enumerable: true, get: function () { return tool_registry_js_1.guardToolRegistry; } });
|
|
63
63
|
Object.defineProperty(exports, "RegistryConstructionError", { enumerable: true, get: function () { return tool_registry_js_1.RegistryConstructionError; } });
|
|
64
|
+
// repo-merge-gate (#7) — the PURE repo-side merge decision (Placement B). No I/O; protection is input.
|
|
65
|
+
var merge_gate_js_1 = require("./merge-gate.js");
|
|
66
|
+
Object.defineProperty(exports, "gateDecision", { enumerable: true, get: function () { return merge_gate_js_1.gateDecision; } });
|
|
67
|
+
// deploy-gate (#8) — the PURE deploy authorization decision. No I/O; pipeline enforcement is input.
|
|
68
|
+
var deploy_gate_js_1 = require("./deploy-gate.js");
|
|
69
|
+
Object.defineProperty(exports, "deployGate", { enumerable: true, get: function () { return deploy_gate_js_1.deployGate; } });
|
|
64
70
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AAEH,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AACtB,6CAAkE;AAAzD,8GAAA,eAAe,OAAA;AAAE,+GAAA,gBAAgB,OAAA;AAC1C,2FAA2F;AAC3F,2DAA6F;AAApF,2HAAA,qBAAqB,OAAA;AAAE,qHAAA,eAAe,OAAA;AAAE,mHAAA,aAAa,OAAA;AAE9D,sGAAsG;AACtG,+DAA+D;AAC/D,6DAA0G;AAAjG,uHAAA,gBAAgB,OAAA;AAAE,4HAAA,qBAAqB,OAAA;AAAE,+HAAA,wBAAwB,OAAA;AAE1E,sCAA8C;AAArC,mGAAA,YAAY,OAAA;AACrB,+FAA+F;AAC/F,uDAG4B;AAF1B,uHAAA,mBAAmB,OAAA;AAAE,yHAAA,qBAAqB,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,4GAAA,QAAQ,OAAA;AAAE,kHAAA,cAAc,OAAA;AACnF,qHAAA,iBAAiB,OAAA;AAAE,gHAAA,YAAY,OAAA;AAAE,mHAAA,eAAe,OAAA;AAAE,6GAAA,SAAS,OAAA;AAAE,mHAAA,eAAe,OAAA;AA2B9E,kGAAkG;AAClG,wFAAwF;AACxF,+DAAqE;AAA5D,wHAAA,OAAO,OAAoB;AAKpC,uDAA6D;AAApD,6GAAA,SAAS,OAAA;AAAE,gHAAA,YAAY,OAAA;AAEhC,qGAAqG;AACrG,uDAAkF;AAAzE,qHAAA,iBAAiB,OAAA;AAAE,6HAAA,yBAAyB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AAEH,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AACtB,6CAAkE;AAAzD,8GAAA,eAAe,OAAA;AAAE,+GAAA,gBAAgB,OAAA;AAC1C,2FAA2F;AAC3F,2DAA6F;AAApF,2HAAA,qBAAqB,OAAA;AAAE,qHAAA,eAAe,OAAA;AAAE,mHAAA,aAAa,OAAA;AAE9D,sGAAsG;AACtG,+DAA+D;AAC/D,6DAA0G;AAAjG,uHAAA,gBAAgB,OAAA;AAAE,4HAAA,qBAAqB,OAAA;AAAE,+HAAA,wBAAwB,OAAA;AAE1E,sCAA8C;AAArC,mGAAA,YAAY,OAAA;AACrB,+FAA+F;AAC/F,uDAG4B;AAF1B,uHAAA,mBAAmB,OAAA;AAAE,yHAAA,qBAAqB,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,4GAAA,QAAQ,OAAA;AAAE,kHAAA,cAAc,OAAA;AACnF,qHAAA,iBAAiB,OAAA;AAAE,gHAAA,YAAY,OAAA;AAAE,mHAAA,eAAe,OAAA;AAAE,6GAAA,SAAS,OAAA;AAAE,mHAAA,eAAe,OAAA;AA2B9E,kGAAkG;AAClG,wFAAwF;AACxF,+DAAqE;AAA5D,wHAAA,OAAO,OAAoB;AAKpC,uDAA6D;AAApD,6GAAA,SAAS,OAAA;AAAE,gHAAA,YAAY,OAAA;AAEhC,qGAAqG;AACrG,uDAAkF;AAAzE,qHAAA,iBAAiB,OAAA;AAAE,6HAAA,yBAAyB,OAAA;AAarD,uGAAuG;AACvG,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AAMrB,oGAAoG;AACpG,mDAA8C;AAArC,4GAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repo-merge-gate (#7) — the PURE gate decision (repo-merge-gate-SPEC §1). Placement B: the repo-side
|
|
3
|
+
* required-status-check logic that answers "may THIS PR head merge, given the finished receipt and the
|
|
4
|
+
* observed branch-protection state?"
|
|
5
|
+
*
|
|
6
|
+
* This is a PURE function — it performs NO I/O and makes NO GitHub call (branch protection is an
|
|
7
|
+
* INPUT, observed by the host and passed in). It does NOT re-decide: it reads only the finished
|
|
8
|
+
* receipt fields (decision / lifecycle authz / binding) — the deterministic verdict stays the
|
|
9
|
+
* server's (M5). Same inputs → same output (M8).
|
|
10
|
+
*
|
|
11
|
+
* Scope honesty is the whole point (§4): `inescapable_merge` is true ONLY in the fully-enforcing,
|
|
12
|
+
* non-bypassable case. For absent / advisory / unknown protection, or when an admin can bypass the
|
|
13
|
+
* required check, the check may still be `success` for visibility but `inescapable_merge` is FALSE
|
|
14
|
+
* with the residual named. It never claims more than the platform actually enforces.
|
|
15
|
+
*
|
|
16
|
+
* Public source (ships in the npm package): it references only receipt/protection fields and generic
|
|
17
|
+
* merge-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or secrets.
|
|
18
|
+
*/
|
|
19
|
+
export type GateStatusState = 'success' | 'failure' | 'pending';
|
|
20
|
+
/** The 14 gate reason codes (§1.1). Success uses allow_current_head; the protection_* / admin_bypass_open
|
|
21
|
+
* codes name the honesty residual; server_unreachable is a host-supplied fail-closed policy input. */
|
|
22
|
+
export type GateReason = 'allow_current_head' | 'no_receipt' | 'receipt_not_authorized' | 'stale_head' | 'fingerprint_mismatch' | 'operation_mismatch' | 'target_mismatch' | 'decision_not_allow' | 'body_hash_mismatch' | 'protection_not_configured' | 'protection_advisory_only' | 'admin_bypass_open' | 'inputs_incomplete' | 'server_unreachable';
|
|
23
|
+
export type EnforcementState = 'ENFORCING' | 'ADVISORY' | 'UNKNOWN' | 'ABSENT';
|
|
24
|
+
/** A view of the finished receipt (fields already produced by the server + lifecycle evaluation).
|
|
25
|
+
* Reuses the shipped field names — the gate never re-derives any of them. */
|
|
26
|
+
export type ReceiptView = {
|
|
27
|
+
/** Lifecycle authorization result (NOT signature validity alone). */
|
|
28
|
+
currently_authorized: boolean;
|
|
29
|
+
authz_reason?: string;
|
|
30
|
+
decision: 'ALLOW' | 'WARN' | 'REQUIRE_APPROVAL' | 'BLOCK' | string;
|
|
31
|
+
execution_action?: 'CONTINUE' | 'CONTINUE_WITH_MONITORING' | 'REQUEST_APPROVAL' | 'STOP' | string;
|
|
32
|
+
/** The PR head commit this receipt was issued for (required for a non-null receipt). */
|
|
33
|
+
bound_head_sha: string;
|
|
34
|
+
verdict_fingerprint?: string;
|
|
35
|
+
body_hash?: string;
|
|
36
|
+
operation?: 'merge' | 'deploy' | 'tool_call' | 'publish' | string;
|
|
37
|
+
target_id?: string;
|
|
38
|
+
environment?: string;
|
|
39
|
+
/** Structural signature ok — NOT sufficient alone (currently_authorized is the gate). */
|
|
40
|
+
signature_valid?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** Branch-protection observation (from the host's GitHub read — the pure gate consumes it as input). */
|
|
43
|
+
export type ProtectionState = {
|
|
44
|
+
enforcement: EnforcementState;
|
|
45
|
+
admin_bypass_possible: boolean;
|
|
46
|
+
required_context_name?: string;
|
|
47
|
+
};
|
|
48
|
+
export type RequiredContext = {
|
|
49
|
+
operation?: string;
|
|
50
|
+
repository?: string;
|
|
51
|
+
expected_fingerprint?: string;
|
|
52
|
+
expected_body_hash?: string;
|
|
53
|
+
protection: ProtectionState;
|
|
54
|
+
allowWarnMerge?: boolean;
|
|
55
|
+
allowPrefixCompare?: boolean;
|
|
56
|
+
allowPending?: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type GateDecisionInput = {
|
|
59
|
+
prHeadSha: string;
|
|
60
|
+
receipt: ReceiptView | null;
|
|
61
|
+
requiredContext: RequiredContext;
|
|
62
|
+
/** Incomplete/no-receipt → pending when true, else failure (default false). */
|
|
63
|
+
allowPending?: boolean;
|
|
64
|
+
/** Green the gate on WARN as well as ALLOW (default false). */
|
|
65
|
+
allowWarnMerge?: boolean;
|
|
66
|
+
/** Compare head SHAs by prefix instead of full 40-char equality (default false). */
|
|
67
|
+
allowPrefixCompare?: boolean;
|
|
68
|
+
};
|
|
69
|
+
export type GateDecision = {
|
|
70
|
+
merge_allowed: boolean;
|
|
71
|
+
state: GateStatusState;
|
|
72
|
+
reason: GateReason;
|
|
73
|
+
enforcement_state: EnforcementState;
|
|
74
|
+
inescapable_merge: boolean;
|
|
75
|
+
/** Named residual when the check is green but NOT inescapable (honesty channel, §4.2). */
|
|
76
|
+
residual?: GateReason;
|
|
77
|
+
detail: {
|
|
78
|
+
prHeadSha: string;
|
|
79
|
+
bound_head_sha: string | null;
|
|
80
|
+
decision: string | null;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* §1.4 — the normative, first-match-wins gate decision. Pure; no I/O; deterministic.
|
|
85
|
+
*/
|
|
86
|
+
export declare function gateDecision(input: GateDecisionInput): GateDecision;
|
|
87
|
+
//# sourceMappingURL=merge-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-gate.d.ts","sourceRoot":"","sources":["../../src/merge-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhE;uGACuG;AACvG,MAAM,MAAM,UAAU,GAClB,oBAAoB,GACpB,YAAY,GACZ,wBAAwB,GACxB,YAAY,GACZ,sBAAsB,GACtB,oBAAoB,GACpB,iBAAiB,GACjB,oBAAoB,GACpB,oBAAoB,GACpB,2BAA2B,GAC3B,0BAA0B,GAC1B,mBAAmB,GACnB,mBAAmB,GACnB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/E;8EAC8E;AAC9E,MAAM,MAAM,WAAW,GAAG;IACxB,qEAAqE;IACrE,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAC;IACnE,gBAAgB,CAAC,EAAE,UAAU,GAAG,0BAA0B,GAAG,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAC;IAClG,wFAAwF;IACxF,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wGAAwG;AACxG,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,gBAAgB,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,eAAe,CAAC;IAE5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,eAAe,CAAC;IACjC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACvF,CAAC;AAkCF;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CA6EnE"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* repo-merge-gate (#7) — the PURE gate decision (repo-merge-gate-SPEC §1). Placement B: the repo-side
|
|
4
|
+
* required-status-check logic that answers "may THIS PR head merge, given the finished receipt and the
|
|
5
|
+
* observed branch-protection state?"
|
|
6
|
+
*
|
|
7
|
+
* This is a PURE function — it performs NO I/O and makes NO GitHub call (branch protection is an
|
|
8
|
+
* INPUT, observed by the host and passed in). It does NOT re-decide: it reads only the finished
|
|
9
|
+
* receipt fields (decision / lifecycle authz / binding) — the deterministic verdict stays the
|
|
10
|
+
* server's (M5). Same inputs → same output (M8).
|
|
11
|
+
*
|
|
12
|
+
* Scope honesty is the whole point (§4): `inescapable_merge` is true ONLY in the fully-enforcing,
|
|
13
|
+
* non-bypassable case. For absent / advisory / unknown protection, or when an admin can bypass the
|
|
14
|
+
* required check, the check may still be `success` for visibility but `inescapable_merge` is FALSE
|
|
15
|
+
* with the residual named. It never claims more than the platform actually enforces.
|
|
16
|
+
*
|
|
17
|
+
* Public source (ships in the npm package): it references only receipt/protection fields and generic
|
|
18
|
+
* merge-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or secrets.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.gateDecision = gateDecision;
|
|
22
|
+
// ── helpers (pure) ────────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
function normSha(s) {
|
|
24
|
+
return String(s == null ? '' : s).trim().toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
/** §1.2 — full lowercase SHA equality by default; prefix comparison is opt-in. */
|
|
27
|
+
function sameHead(a, b, allowPrefix) {
|
|
28
|
+
const na = normSha(a);
|
|
29
|
+
const nb = normSha(b);
|
|
30
|
+
if (!na || !nb)
|
|
31
|
+
return false;
|
|
32
|
+
if (na === nb)
|
|
33
|
+
return true;
|
|
34
|
+
if (allowPrefix && na.length >= 7 && nb.length >= 7 && (na.startsWith(nb) || nb.startsWith(na)))
|
|
35
|
+
return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/** §1.3 — allow-class for the merge gate: ALLOW (or WARN only when opted in) + a continue-class action. */
|
|
39
|
+
function isAllowClass(receipt, allowWarnMerge) {
|
|
40
|
+
const dec = receipt.decision;
|
|
41
|
+
const decisionOk = dec === 'ALLOW' || (dec === 'WARN' && allowWarnMerge === true);
|
|
42
|
+
if (!decisionOk)
|
|
43
|
+
return false;
|
|
44
|
+
const ea = receipt.execution_action;
|
|
45
|
+
if (ea === undefined || ea === null || ea === '')
|
|
46
|
+
return true; // decision already allow-class, no action to contradict it
|
|
47
|
+
return ea === 'CONTINUE' || ea === 'CONTINUE_WITH_MONITORING';
|
|
48
|
+
}
|
|
49
|
+
/** Target/repo binding: the receipt's target must name this repository (exact or `repo:`/path/colon suffix). */
|
|
50
|
+
function targetMatches(targetId, repository) {
|
|
51
|
+
const t = String(targetId).toLowerCase();
|
|
52
|
+
const r = String(repository).toLowerCase();
|
|
53
|
+
return t === r || t === `repo:${r}` || t.endsWith(`:${r}`) || t.endsWith(`/${r}`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* §1.4 — the normative, first-match-wins gate decision. Pure; no I/O; deterministic.
|
|
57
|
+
*/
|
|
58
|
+
function gateDecision(input) {
|
|
59
|
+
const rc = input.requiredContext || {};
|
|
60
|
+
const protection = rc.protection || { enforcement: 'UNKNOWN', admin_bypass_possible: true };
|
|
61
|
+
const enforcement_state = protection.enforcement;
|
|
62
|
+
const allowPending = input.allowPending ?? rc.allowPending ?? false;
|
|
63
|
+
const allowWarnMerge = input.allowWarnMerge ?? rc.allowWarnMerge ?? false;
|
|
64
|
+
const allowPrefix = input.allowPrefixCompare ?? rc.allowPrefixCompare ?? false;
|
|
65
|
+
const receipt = input.receipt;
|
|
66
|
+
const detail = {
|
|
67
|
+
prHeadSha: normSha(input.prHeadSha),
|
|
68
|
+
bound_head_sha: receipt ? normSha(receipt.bound_head_sha) : null,
|
|
69
|
+
decision: receipt ? String(receipt.decision) : null,
|
|
70
|
+
};
|
|
71
|
+
const fail = (state, reason) => ({
|
|
72
|
+
merge_allowed: false, state, reason, enforcement_state, inescapable_merge: false, detail,
|
|
73
|
+
});
|
|
74
|
+
// 1) incomplete head → pending/failure (fail-closed on missing evaluation input).
|
|
75
|
+
if (!input.prHeadSha || String(input.prHeadSha).trim() === '') {
|
|
76
|
+
return fail(allowPending ? 'pending' : 'failure', 'inputs_incomplete');
|
|
77
|
+
}
|
|
78
|
+
// 2) no receipt → fail-closed (M2).
|
|
79
|
+
if (receipt === null || receipt === undefined) {
|
|
80
|
+
return fail(allowPending ? 'pending' : 'failure', 'no_receipt');
|
|
81
|
+
}
|
|
82
|
+
// 3) lifecycle authorization — a valid signature alone is NOT sufficient (M3).
|
|
83
|
+
if (receipt.currently_authorized !== true) {
|
|
84
|
+
return fail('failure', 'receipt_not_authorized');
|
|
85
|
+
}
|
|
86
|
+
// 4) operation binding — a receipt for a different operation does not authorize this gate.
|
|
87
|
+
const op = rc.operation ?? 'merge';
|
|
88
|
+
if (receipt.operation != null && receipt.operation !== op) {
|
|
89
|
+
return fail('failure', 'operation_mismatch');
|
|
90
|
+
}
|
|
91
|
+
// 5) STALE HEAD — the central invariant: a receipt bound to an old head never greens a new one (M1).
|
|
92
|
+
if (!sameHead(receipt.bound_head_sha, input.prHeadSha, allowPrefix)) {
|
|
93
|
+
return fail('failure', 'stale_head');
|
|
94
|
+
}
|
|
95
|
+
// 6) optional re-bind to the current change set.
|
|
96
|
+
if (rc.expected_fingerprint != null && receipt.verdict_fingerprint !== rc.expected_fingerprint) {
|
|
97
|
+
return fail('failure', 'fingerprint_mismatch');
|
|
98
|
+
}
|
|
99
|
+
if (rc.expected_body_hash != null && receipt.body_hash !== rc.expected_body_hash) {
|
|
100
|
+
return fail('failure', 'body_hash_mismatch');
|
|
101
|
+
}
|
|
102
|
+
// 7) target / repository binding (only when both sides are present).
|
|
103
|
+
if (rc.repository && receipt.target_id && !targetMatches(receipt.target_id, rc.repository)) {
|
|
104
|
+
return fail('failure', 'target_mismatch');
|
|
105
|
+
}
|
|
106
|
+
// 8) decision class — only an allow-class verdict greens the merge gate (M4).
|
|
107
|
+
if (!isAllowClass(receipt, allowWarnMerge)) {
|
|
108
|
+
return fail('failure', 'decision_not_allow');
|
|
109
|
+
}
|
|
110
|
+
// 9) success for the CHECK — green for visibility regardless of protection strength.
|
|
111
|
+
// 10) inescapable_merge claim (STRICT, §1.4 step 10 / M6): only when the platform actually enforces
|
|
112
|
+
// the required check AND no admin can bypass it. Never true otherwise.
|
|
113
|
+
const inescapable_merge = enforcement_state === 'ENFORCING' && protection.admin_bypass_possible === false;
|
|
114
|
+
let residual;
|
|
115
|
+
if (!inescapable_merge) {
|
|
116
|
+
if (enforcement_state === 'ENFORCING')
|
|
117
|
+
residual = 'admin_bypass_open'; // required, but admins can override
|
|
118
|
+
else if (enforcement_state === 'ADVISORY')
|
|
119
|
+
residual = 'protection_advisory_only'; // posted but not required
|
|
120
|
+
else
|
|
121
|
+
residual = 'protection_not_configured'; // ABSENT or UNKNOWN — cannot attest
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
merge_allowed: true,
|
|
125
|
+
state: 'success',
|
|
126
|
+
reason: 'allow_current_head',
|
|
127
|
+
enforcement_state,
|
|
128
|
+
inescapable_merge,
|
|
129
|
+
...(residual ? { residual } : {}),
|
|
130
|
+
detail,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=merge-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-gate.js","sourceRoot":"","sources":["../../src/merge-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deploy-gate (#8) — the PURE deploy authorization decision (deploy-gate-SPEC §1). The DEPLOY sibling
|
|
3
|
+
* of the merge gate: it answers "may THIS artifact deploy to THIS environment, given the finished
|
|
4
|
+
* receipt and the observed pipeline-enforcement state?"
|
|
5
|
+
*
|
|
6
|
+
* Same shape as the merge gate, different operation. It is a PURE function — NO I/O, NO CD-API call
|
|
7
|
+
* (pipeline enforcement is an INPUT, observed by the host and passed in). It does NOT re-decide: it
|
|
8
|
+
* reads only the finished receipt fields — the deterministic verdict stays the server's (D7). Same
|
|
9
|
+
* inputs → same output (D9).
|
|
10
|
+
*
|
|
11
|
+
* The three deploy-specific binds are the whole point: T7 (a MERGE — or operation-less — receipt can
|
|
12
|
+
* never deploy), environment binding (a staging ALLOW never authorizes production), and artifact
|
|
13
|
+
* binding (an old artifact's ALLOW never deploys a newer one). Scope honesty mirrors the merge gate:
|
|
14
|
+
* `inescapable_deploy` is true ONLY in the fully-enforcing, non-bypassable case; otherwise the check
|
|
15
|
+
* may be `success` for visibility but the claim is false with the residual named.
|
|
16
|
+
*
|
|
17
|
+
* Public source (ships in the npm package): references only receipt/target/enforcement fields and
|
|
18
|
+
* generic deploy-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or
|
|
19
|
+
* secrets.
|
|
20
|
+
*/
|
|
21
|
+
import type { GateStatusState, EnforcementState } from './merge-gate.js';
|
|
22
|
+
export type { GateStatusState, EnforcementState } from './merge-gate.js';
|
|
23
|
+
/** The 13 deploy-gate reason codes (§1.1). Success uses allow_current_deploy; enforcement_not_configured
|
|
24
|
+
* and bypass_open name the honesty residual on an otherwise-green check. */
|
|
25
|
+
export type DeployGateReason = 'allow_current_deploy' | 'no_receipt' | 'receipt_not_authorized' | 'operation_mismatch' | 'env_mismatch' | 'stale_artifact' | 'fingerprint_mismatch' | 'body_hash_mismatch' | 'target_mismatch' | 'decision_not_allow' | 'inputs_incomplete' | 'enforcement_not_configured' | 'bypass_open';
|
|
26
|
+
/** What is being deployed — all fields are INPUTS (the pure function performs no discovery). */
|
|
27
|
+
export type DeployTarget = {
|
|
28
|
+
environment: string;
|
|
29
|
+
/** Immutable artifact identity: content digest, or a commit SHA when that is the deploy unit. */
|
|
30
|
+
artifact_id: string;
|
|
31
|
+
service?: string;
|
|
32
|
+
pipeline_run_id?: string;
|
|
33
|
+
};
|
|
34
|
+
/** A view of the finished receipt. Reuses the shipped field names + the deploy analogs of bound_head_sha. */
|
|
35
|
+
export type DeployReceiptView = {
|
|
36
|
+
currently_authorized: boolean;
|
|
37
|
+
authz_reason?: string;
|
|
38
|
+
decision: 'ALLOW' | 'WARN' | 'REQUIRE_APPROVAL' | 'BLOCK' | string;
|
|
39
|
+
execution_action?: 'CONTINUE' | 'CONTINUE_WITH_MONITORING' | 'REQUEST_APPROVAL' | 'STOP' | string;
|
|
40
|
+
/** MUST be 'deploy' for success (T7). */
|
|
41
|
+
operation?: 'merge' | 'deploy' | 'tool_call' | 'publish' | string;
|
|
42
|
+
/** The environment this receipt authorizes (deploy analog of bound_head_sha). */
|
|
43
|
+
bound_environment?: string | null;
|
|
44
|
+
/** The artifact/commit this receipt was issued for (deploy analog of bound_head_sha). */
|
|
45
|
+
bound_artifact_id?: string | null;
|
|
46
|
+
verdict_fingerprint?: string;
|
|
47
|
+
body_hash?: string;
|
|
48
|
+
target_id?: string;
|
|
49
|
+
signature_valid?: boolean;
|
|
50
|
+
};
|
|
51
|
+
/** Pipeline-enforcement observation (from the host's CD-config read — the pure gate consumes it). */
|
|
52
|
+
export type DeployEnforcementState = {
|
|
53
|
+
enforcement: EnforcementState;
|
|
54
|
+
bypass_possible: boolean;
|
|
55
|
+
required_step_name?: string;
|
|
56
|
+
};
|
|
57
|
+
export type DeployRequiredContext = {
|
|
58
|
+
operation?: 'deploy' | string;
|
|
59
|
+
repository?: string;
|
|
60
|
+
service?: string;
|
|
61
|
+
expected_fingerprint?: string;
|
|
62
|
+
expected_body_hash?: string;
|
|
63
|
+
enforcement: DeployEnforcementState;
|
|
64
|
+
/** Default true: a missing bound_environment on the receipt → env_mismatch. */
|
|
65
|
+
require_bound_environment?: boolean;
|
|
66
|
+
/** Default true: a missing bound_artifact_id → stale_artifact. */
|
|
67
|
+
require_bound_artifact?: boolean;
|
|
68
|
+
allowWarnDeploy?: boolean;
|
|
69
|
+
allowPrefixCompare?: boolean;
|
|
70
|
+
allowPending?: boolean;
|
|
71
|
+
};
|
|
72
|
+
export type DeployGateInput = {
|
|
73
|
+
deployTarget: DeployTarget;
|
|
74
|
+
receipt: DeployReceiptView | null;
|
|
75
|
+
requiredContext: DeployRequiredContext;
|
|
76
|
+
/** Incomplete/no-receipt → pending when true, else failure (default false). */
|
|
77
|
+
allowPending?: boolean;
|
|
78
|
+
/** Green the gate on WARN as well as ALLOW (default false; stricter for prod-shaped envs). */
|
|
79
|
+
allowWarnDeploy?: boolean;
|
|
80
|
+
/** Compare env/artifact by prefix instead of full equality (default false). */
|
|
81
|
+
allowPrefixCompare?: boolean;
|
|
82
|
+
};
|
|
83
|
+
export type DeployGateDecision = {
|
|
84
|
+
deploy_allowed: boolean;
|
|
85
|
+
state: GateStatusState;
|
|
86
|
+
reason: DeployGateReason;
|
|
87
|
+
enforcement_state: EnforcementState;
|
|
88
|
+
inescapable_deploy: boolean;
|
|
89
|
+
/** Named residual when the check is green but NOT inescapable (honesty channel, §3.2). */
|
|
90
|
+
residual?: DeployGateReason;
|
|
91
|
+
detail: {
|
|
92
|
+
environment: string;
|
|
93
|
+
artifact_id: string;
|
|
94
|
+
bound_environment: string | null;
|
|
95
|
+
bound_artifact_id: string | null;
|
|
96
|
+
operation: string | null;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* §1.4 — the normative, first-match-wins deploy decision. Pure; no I/O; deterministic.
|
|
101
|
+
*/
|
|
102
|
+
export declare function deployGate(input: DeployGateInput): DeployGateDecision;
|
|
103
|
+
//# sourceMappingURL=deploy-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-gate.d.ts","sourceRoot":"","sources":["../../src/deploy-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deploy-gate (#8) — the PURE deploy authorization decision (deploy-gate-SPEC §1). The DEPLOY sibling
|
|
3
|
+
* of the merge gate: it answers "may THIS artifact deploy to THIS environment, given the finished
|
|
4
|
+
* receipt and the observed pipeline-enforcement state?"
|
|
5
|
+
*
|
|
6
|
+
* Same shape as the merge gate, different operation. It is a PURE function — NO I/O, NO CD-API call
|
|
7
|
+
* (pipeline enforcement is an INPUT, observed by the host and passed in). It does NOT re-decide: it
|
|
8
|
+
* reads only the finished receipt fields — the deterministic verdict stays the server's (D7). Same
|
|
9
|
+
* inputs → same output (D9).
|
|
10
|
+
*
|
|
11
|
+
* The three deploy-specific binds are the whole point: T7 (a MERGE — or operation-less — receipt can
|
|
12
|
+
* never deploy), environment binding (a staging ALLOW never authorizes production), and artifact
|
|
13
|
+
* binding (an old artifact's ALLOW never deploys a newer one). Scope honesty mirrors the merge gate:
|
|
14
|
+
* `inescapable_deploy` is true ONLY in the fully-enforcing, non-bypassable case; otherwise the check
|
|
15
|
+
* may be `success` for visibility but the claim is false with the residual named.
|
|
16
|
+
*
|
|
17
|
+
* Public source (ships in the npm package): references only receipt/target/enforcement fields and
|
|
18
|
+
* generic deploy-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or
|
|
19
|
+
* secrets.
|
|
20
|
+
*/
|
|
21
|
+
// ── helpers (pure) ────────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
function norm(s) {
|
|
23
|
+
return String(s == null ? '' : s).trim().toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
/** §1.2 — full lowercase equality by default (env exact; artifact digest/sha); prefix opt-in. */
|
|
26
|
+
function sameNorm(a, b, allowPrefix) {
|
|
27
|
+
const na = norm(a);
|
|
28
|
+
const nb = norm(b);
|
|
29
|
+
if (!na || !nb)
|
|
30
|
+
return false;
|
|
31
|
+
if (na === nb)
|
|
32
|
+
return true;
|
|
33
|
+
if (allowPrefix && na.length >= 7 && nb.length >= 7 && (na.startsWith(nb) || nb.startsWith(na)))
|
|
34
|
+
return true;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
/** §1.3 — allow-class for deploy: ALLOW (or WARN only when opted in) + a continue-class action. */
|
|
38
|
+
function isAllowClass(receipt, allowWarnDeploy) {
|
|
39
|
+
const dec = receipt.decision;
|
|
40
|
+
const decisionOk = dec === 'ALLOW' || (dec === 'WARN' && allowWarnDeploy === true);
|
|
41
|
+
if (!decisionOk)
|
|
42
|
+
return false;
|
|
43
|
+
const ea = receipt.execution_action;
|
|
44
|
+
if (ea === undefined || ea === null || ea === '')
|
|
45
|
+
return true;
|
|
46
|
+
return ea === 'CONTINUE' || ea === 'CONTINUE_WITH_MONITORING';
|
|
47
|
+
}
|
|
48
|
+
/** target_id must name this service/repository (exact or `svc:`/`repo:`/path/colon suffix). */
|
|
49
|
+
function idMatchesName(targetId, name) {
|
|
50
|
+
const t = norm(targetId);
|
|
51
|
+
const r = norm(name);
|
|
52
|
+
return t === r || t === `svc:${r}` || t === `repo:${r}` || t.endsWith(`:${r}`) || t.endsWith(`/${r}`);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* §1.4 — the normative, first-match-wins deploy decision. Pure; no I/O; deterministic.
|
|
56
|
+
*/
|
|
57
|
+
export function deployGate(input) {
|
|
58
|
+
const target = input.deployTarget || {};
|
|
59
|
+
const rc = input.requiredContext || {};
|
|
60
|
+
const enf = rc.enforcement || { enforcement: 'UNKNOWN', bypass_possible: true };
|
|
61
|
+
const enforcement_state = enf.enforcement;
|
|
62
|
+
const opRequired = rc.operation ?? 'deploy';
|
|
63
|
+
const requireEnv = rc.require_bound_environment !== false; // default true
|
|
64
|
+
const requireArt = rc.require_bound_artifact !== false; // default true
|
|
65
|
+
const allowPending = input.allowPending ?? rc.allowPending ?? false;
|
|
66
|
+
const allowWarnDeploy = input.allowWarnDeploy ?? rc.allowWarnDeploy ?? false;
|
|
67
|
+
const allowPrefix = input.allowPrefixCompare ?? rc.allowPrefixCompare ?? false;
|
|
68
|
+
const receipt = input.receipt;
|
|
69
|
+
const detail = {
|
|
70
|
+
environment: norm(target.environment),
|
|
71
|
+
artifact_id: norm(target.artifact_id),
|
|
72
|
+
bound_environment: receipt && receipt.bound_environment != null ? norm(receipt.bound_environment) : null,
|
|
73
|
+
bound_artifact_id: receipt && receipt.bound_artifact_id != null ? norm(receipt.bound_artifact_id) : null,
|
|
74
|
+
operation: receipt && receipt.operation != null ? String(receipt.operation) : null,
|
|
75
|
+
};
|
|
76
|
+
const deny = (state, reason) => ({
|
|
77
|
+
deploy_allowed: false, state, reason, enforcement_state, inescapable_deploy: false, detail,
|
|
78
|
+
});
|
|
79
|
+
// 0) incomplete target → pending/failure (fail-closed on missing evaluation input).
|
|
80
|
+
if (!target.environment || String(target.environment).trim() === ''
|
|
81
|
+
|| !target.artifact_id || String(target.artifact_id).trim() === '') {
|
|
82
|
+
return deny(allowPending ? 'pending' : 'failure', 'inputs_incomplete');
|
|
83
|
+
}
|
|
84
|
+
// 1) no receipt → fail-closed (D4).
|
|
85
|
+
if (receipt === null || receipt === undefined) {
|
|
86
|
+
return deny(allowPending ? 'pending' : 'failure', 'no_receipt');
|
|
87
|
+
}
|
|
88
|
+
// 2) lifecycle authorization — a valid signature alone is NOT sufficient (D5).
|
|
89
|
+
if (receipt.currently_authorized !== true) {
|
|
90
|
+
return deny('failure', 'receipt_not_authorized');
|
|
91
|
+
}
|
|
92
|
+
// 3) T7 — operation MUST be deploy. A merge (or any other) receipt cannot deploy, and a MISSING
|
|
93
|
+
// operation also fails closed (stricter than the merge gate — deploy is higher-risk) (D1).
|
|
94
|
+
if (receipt.operation == null || norm(receipt.operation) !== norm(opRequired)) {
|
|
95
|
+
return deny('failure', 'operation_mismatch');
|
|
96
|
+
}
|
|
97
|
+
// 4) ENVIRONMENT binding — a staging ALLOW never authorizes production (D2).
|
|
98
|
+
if (requireEnv) {
|
|
99
|
+
if (!receipt.bound_environment || !sameNorm(receipt.bound_environment, target.environment, allowPrefix)) {
|
|
100
|
+
return deny('failure', 'env_mismatch');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (receipt.bound_environment && !sameNorm(receipt.bound_environment, target.environment, allowPrefix)) {
|
|
104
|
+
return deny('failure', 'env_mismatch');
|
|
105
|
+
}
|
|
106
|
+
// 5) ARTIFACT binding — an old artifact's ALLOW never deploys a newer one (D3, the stale analog).
|
|
107
|
+
if (requireArt) {
|
|
108
|
+
if (!receipt.bound_artifact_id || !sameNorm(receipt.bound_artifact_id, target.artifact_id, allowPrefix)) {
|
|
109
|
+
return deny('failure', 'stale_artifact');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (receipt.bound_artifact_id && !sameNorm(receipt.bound_artifact_id, target.artifact_id, allowPrefix)) {
|
|
113
|
+
return deny('failure', 'stale_artifact');
|
|
114
|
+
}
|
|
115
|
+
// 6) optional re-bind to the current change set.
|
|
116
|
+
if (rc.expected_fingerprint != null && receipt.verdict_fingerprint !== rc.expected_fingerprint) {
|
|
117
|
+
return deny('failure', 'fingerprint_mismatch');
|
|
118
|
+
}
|
|
119
|
+
if (rc.expected_body_hash != null && receipt.body_hash !== rc.expected_body_hash) {
|
|
120
|
+
return deny('failure', 'body_hash_mismatch');
|
|
121
|
+
}
|
|
122
|
+
// 7) optional service / repository target binding.
|
|
123
|
+
if (rc.service && receipt.target_id) {
|
|
124
|
+
if (!idMatchesName(receipt.target_id, rc.service))
|
|
125
|
+
return deny('failure', 'target_mismatch');
|
|
126
|
+
}
|
|
127
|
+
else if (rc.repository && receipt.target_id) {
|
|
128
|
+
if (!idMatchesName(receipt.target_id, rc.repository))
|
|
129
|
+
return deny('failure', 'target_mismatch');
|
|
130
|
+
}
|
|
131
|
+
// 8) decision class — only an allow-class verdict greens the deploy gate (D6).
|
|
132
|
+
if (!isAllowClass(receipt, allowWarnDeploy)) {
|
|
133
|
+
return deny('failure', 'decision_not_allow');
|
|
134
|
+
}
|
|
135
|
+
// 9) success for the CHECK / pipeline step — green for visibility regardless of enforcement strength.
|
|
136
|
+
// 10) inescapable_deploy claim (STRICT, §1.4 step 10 / D8): only when the pipeline actually enforces
|
|
137
|
+
// the step AND no bypass is possible. Never true otherwise.
|
|
138
|
+
const inescapable_deploy = enforcement_state === 'ENFORCING' && enf.bypass_possible === false;
|
|
139
|
+
let residual;
|
|
140
|
+
if (!inescapable_deploy) {
|
|
141
|
+
if (enforcement_state === 'ENFORCING')
|
|
142
|
+
residual = 'bypass_open'; // enforced, but a bypass exists
|
|
143
|
+
else
|
|
144
|
+
residual = 'enforcement_not_configured'; // ADVISORY / ABSENT / UNKNOWN
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
deploy_allowed: true,
|
|
148
|
+
state: 'success',
|
|
149
|
+
reason: 'allow_current_deploy',
|
|
150
|
+
enforcement_state,
|
|
151
|
+
inescapable_deploy,
|
|
152
|
+
...(residual ? { residual } : {}),
|
|
153
|
+
detail,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=deploy-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-gate.js","sourceRoot":"","sources":["../../src/deploy-gate.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -32,4 +32,8 @@ export type { ResolveInput, ResolveConfig, ResolveResult, ResolvedArtifact, Reso
|
|
|
32
32
|
export { matchGlob, globToRegExp } from './resolver-glob.js';
|
|
33
33
|
export { guardToolRegistry, RegistryConstructionError } from './tool-registry.js';
|
|
34
34
|
export type { RawTool, ProtectedTool, ToolMutationClass, EnforcementCoverage, RegistryCoverageReport, GuardToolRegistryConfig, RegistryResult, ToolBinder, RegistryConstructionErrorCode, } from './tool-registry.js';
|
|
35
|
+
export { gateDecision } from './merge-gate.js';
|
|
36
|
+
export type { GateDecisionInput, GateDecision, ReceiptView, RequiredContext, ProtectionState, GateReason, GateStatusState, EnforcementState, } from './merge-gate.js';
|
|
37
|
+
export { deployGate } from './deploy-gate.js';
|
|
38
|
+
export type { DeployGateInput, DeployGateDecision, DeployReceiptView, DeployRequiredContext, DeployEnforcementState, DeployTarget, DeployGateReason, } from './deploy-gate.js';
|
|
35
39
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAGxG,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC1G,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EACnF,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAC7E,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,GAC3G,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,uBAAuB,EACvB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,UAAU,EACV,SAAS,EACT,sBAAsB,EACtB,eAAe,EACf,QAAQ,GACT,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EACV,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAC7E,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,IAAI,oBAAoB,GAClG,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,cAAc,EACd,UAAU,EACV,6BAA6B,GAC9B,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAGxG,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC1G,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EACnF,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAC7E,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,GAC3G,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,uBAAuB,EACvB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,UAAU,EACV,SAAS,EACT,sBAAsB,EACtB,eAAe,EACf,QAAQ,GACT,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EACV,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAC7E,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,IAAI,oBAAoB,GAClG,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EACV,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,cAAc,EACd,UAAU,EACV,6BAA6B,GAC9B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EACV,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAC9E,UAAU,EAAE,eAAe,EAAE,gBAAgB,GAC9C,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,qBAAqB,EAC7E,sBAAsB,EAAE,YAAY,EAAE,gBAAgB,GACvD,MAAM,kBAAkB,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -33,4 +33,8 @@ export { resolve as resolveArtifacts } from './artifact-resolver.js';
|
|
|
33
33
|
export { matchGlob, globToRegExp } from './resolver-glob.js';
|
|
34
34
|
// guardToolRegistry — the agent-runtime inescapability layer ABOVE guardToolCall (Placement A only).
|
|
35
35
|
export { guardToolRegistry, RegistryConstructionError } from './tool-registry.js';
|
|
36
|
+
// repo-merge-gate (#7) — the PURE repo-side merge decision (Placement B). No I/O; protection is input.
|
|
37
|
+
export { gateDecision } from './merge-gate.js';
|
|
38
|
+
// deploy-gate (#8) — the PURE deploy authorization decision. No I/O; pipeline enforcement is input.
|
|
39
|
+
export { deployGate } from './deploy-gate.js';
|
|
36
40
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClE,2FAA2F;AAC3F,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE7F,sGAAsG;AACtG,+DAA+D;AAC/D,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAE1G,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,+FAA+F;AAC/F,OAAO,EACL,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EACnF,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAC7E,MAAM,oBAAoB,CAAC;AA0B5B,kGAAkG;AAClG,wFAAwF;AACxF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAKrE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,qGAAqG;AACrG,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClE,2FAA2F;AAC3F,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE7F,sGAAsG;AACtG,+DAA+D;AAC/D,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAE1G,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,+FAA+F;AAC/F,OAAO,EACL,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EACnF,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAC7E,MAAM,oBAAoB,CAAC;AA0B5B,kGAAkG;AAClG,wFAAwF;AACxF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAKrE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,qGAAqG;AACrG,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAalF,uGAAuG;AACvG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAM/C,oGAAoG;AACpG,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repo-merge-gate (#7) — the PURE gate decision (repo-merge-gate-SPEC §1). Placement B: the repo-side
|
|
3
|
+
* required-status-check logic that answers "may THIS PR head merge, given the finished receipt and the
|
|
4
|
+
* observed branch-protection state?"
|
|
5
|
+
*
|
|
6
|
+
* This is a PURE function — it performs NO I/O and makes NO GitHub call (branch protection is an
|
|
7
|
+
* INPUT, observed by the host and passed in). It does NOT re-decide: it reads only the finished
|
|
8
|
+
* receipt fields (decision / lifecycle authz / binding) — the deterministic verdict stays the
|
|
9
|
+
* server's (M5). Same inputs → same output (M8).
|
|
10
|
+
*
|
|
11
|
+
* Scope honesty is the whole point (§4): `inescapable_merge` is true ONLY in the fully-enforcing,
|
|
12
|
+
* non-bypassable case. For absent / advisory / unknown protection, or when an admin can bypass the
|
|
13
|
+
* required check, the check may still be `success` for visibility but `inescapable_merge` is FALSE
|
|
14
|
+
* with the residual named. It never claims more than the platform actually enforces.
|
|
15
|
+
*
|
|
16
|
+
* Public source (ships in the npm package): it references only receipt/protection fields and generic
|
|
17
|
+
* merge-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or secrets.
|
|
18
|
+
*/
|
|
19
|
+
export type GateStatusState = 'success' | 'failure' | 'pending';
|
|
20
|
+
/** The 14 gate reason codes (§1.1). Success uses allow_current_head; the protection_* / admin_bypass_open
|
|
21
|
+
* codes name the honesty residual; server_unreachable is a host-supplied fail-closed policy input. */
|
|
22
|
+
export type GateReason = 'allow_current_head' | 'no_receipt' | 'receipt_not_authorized' | 'stale_head' | 'fingerprint_mismatch' | 'operation_mismatch' | 'target_mismatch' | 'decision_not_allow' | 'body_hash_mismatch' | 'protection_not_configured' | 'protection_advisory_only' | 'admin_bypass_open' | 'inputs_incomplete' | 'server_unreachable';
|
|
23
|
+
export type EnforcementState = 'ENFORCING' | 'ADVISORY' | 'UNKNOWN' | 'ABSENT';
|
|
24
|
+
/** A view of the finished receipt (fields already produced by the server + lifecycle evaluation).
|
|
25
|
+
* Reuses the shipped field names — the gate never re-derives any of them. */
|
|
26
|
+
export type ReceiptView = {
|
|
27
|
+
/** Lifecycle authorization result (NOT signature validity alone). */
|
|
28
|
+
currently_authorized: boolean;
|
|
29
|
+
authz_reason?: string;
|
|
30
|
+
decision: 'ALLOW' | 'WARN' | 'REQUIRE_APPROVAL' | 'BLOCK' | string;
|
|
31
|
+
execution_action?: 'CONTINUE' | 'CONTINUE_WITH_MONITORING' | 'REQUEST_APPROVAL' | 'STOP' | string;
|
|
32
|
+
/** The PR head commit this receipt was issued for (required for a non-null receipt). */
|
|
33
|
+
bound_head_sha: string;
|
|
34
|
+
verdict_fingerprint?: string;
|
|
35
|
+
body_hash?: string;
|
|
36
|
+
operation?: 'merge' | 'deploy' | 'tool_call' | 'publish' | string;
|
|
37
|
+
target_id?: string;
|
|
38
|
+
environment?: string;
|
|
39
|
+
/** Structural signature ok — NOT sufficient alone (currently_authorized is the gate). */
|
|
40
|
+
signature_valid?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** Branch-protection observation (from the host's GitHub read — the pure gate consumes it as input). */
|
|
43
|
+
export type ProtectionState = {
|
|
44
|
+
enforcement: EnforcementState;
|
|
45
|
+
admin_bypass_possible: boolean;
|
|
46
|
+
required_context_name?: string;
|
|
47
|
+
};
|
|
48
|
+
export type RequiredContext = {
|
|
49
|
+
operation?: string;
|
|
50
|
+
repository?: string;
|
|
51
|
+
expected_fingerprint?: string;
|
|
52
|
+
expected_body_hash?: string;
|
|
53
|
+
protection: ProtectionState;
|
|
54
|
+
allowWarnMerge?: boolean;
|
|
55
|
+
allowPrefixCompare?: boolean;
|
|
56
|
+
allowPending?: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type GateDecisionInput = {
|
|
59
|
+
prHeadSha: string;
|
|
60
|
+
receipt: ReceiptView | null;
|
|
61
|
+
requiredContext: RequiredContext;
|
|
62
|
+
/** Incomplete/no-receipt → pending when true, else failure (default false). */
|
|
63
|
+
allowPending?: boolean;
|
|
64
|
+
/** Green the gate on WARN as well as ALLOW (default false). */
|
|
65
|
+
allowWarnMerge?: boolean;
|
|
66
|
+
/** Compare head SHAs by prefix instead of full 40-char equality (default false). */
|
|
67
|
+
allowPrefixCompare?: boolean;
|
|
68
|
+
};
|
|
69
|
+
export type GateDecision = {
|
|
70
|
+
merge_allowed: boolean;
|
|
71
|
+
state: GateStatusState;
|
|
72
|
+
reason: GateReason;
|
|
73
|
+
enforcement_state: EnforcementState;
|
|
74
|
+
inescapable_merge: boolean;
|
|
75
|
+
/** Named residual when the check is green but NOT inescapable (honesty channel, §4.2). */
|
|
76
|
+
residual?: GateReason;
|
|
77
|
+
detail: {
|
|
78
|
+
prHeadSha: string;
|
|
79
|
+
bound_head_sha: string | null;
|
|
80
|
+
decision: string | null;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* §1.4 — the normative, first-match-wins gate decision. Pure; no I/O; deterministic.
|
|
85
|
+
*/
|
|
86
|
+
export declare function gateDecision(input: GateDecisionInput): GateDecision;
|
|
87
|
+
//# sourceMappingURL=merge-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-gate.d.ts","sourceRoot":"","sources":["../../src/merge-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhE;uGACuG;AACvG,MAAM,MAAM,UAAU,GAClB,oBAAoB,GACpB,YAAY,GACZ,wBAAwB,GACxB,YAAY,GACZ,sBAAsB,GACtB,oBAAoB,GACpB,iBAAiB,GACjB,oBAAoB,GACpB,oBAAoB,GACpB,2BAA2B,GAC3B,0BAA0B,GAC1B,mBAAmB,GACnB,mBAAmB,GACnB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/E;8EAC8E;AAC9E,MAAM,MAAM,WAAW,GAAG;IACxB,qEAAqE;IACrE,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAC;IACnE,gBAAgB,CAAC,EAAE,UAAU,GAAG,0BAA0B,GAAG,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAC;IAClG,wFAAwF;IACxF,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wGAAwG;AACxG,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,gBAAgB,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,eAAe,CAAC;IAE5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,eAAe,CAAC;IACjC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACvF,CAAC;AAkCF;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CA6EnE"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repo-merge-gate (#7) — the PURE gate decision (repo-merge-gate-SPEC §1). Placement B: the repo-side
|
|
3
|
+
* required-status-check logic that answers "may THIS PR head merge, given the finished receipt and the
|
|
4
|
+
* observed branch-protection state?"
|
|
5
|
+
*
|
|
6
|
+
* This is a PURE function — it performs NO I/O and makes NO GitHub call (branch protection is an
|
|
7
|
+
* INPUT, observed by the host and passed in). It does NOT re-decide: it reads only the finished
|
|
8
|
+
* receipt fields (decision / lifecycle authz / binding) — the deterministic verdict stays the
|
|
9
|
+
* server's (M5). Same inputs → same output (M8).
|
|
10
|
+
*
|
|
11
|
+
* Scope honesty is the whole point (§4): `inescapable_merge` is true ONLY in the fully-enforcing,
|
|
12
|
+
* non-bypassable case. For absent / advisory / unknown protection, or when an admin can bypass the
|
|
13
|
+
* required check, the check may still be `success` for visibility but `inescapable_merge` is FALSE
|
|
14
|
+
* with the residual named. It never claims more than the platform actually enforces.
|
|
15
|
+
*
|
|
16
|
+
* Public source (ships in the npm package): it references only receipt/protection fields and generic
|
|
17
|
+
* merge-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or secrets.
|
|
18
|
+
*/
|
|
19
|
+
// ── helpers (pure) ────────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
function normSha(s) {
|
|
21
|
+
return String(s == null ? '' : s).trim().toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
/** §1.2 — full lowercase SHA equality by default; prefix comparison is opt-in. */
|
|
24
|
+
function sameHead(a, b, allowPrefix) {
|
|
25
|
+
const na = normSha(a);
|
|
26
|
+
const nb = normSha(b);
|
|
27
|
+
if (!na || !nb)
|
|
28
|
+
return false;
|
|
29
|
+
if (na === nb)
|
|
30
|
+
return true;
|
|
31
|
+
if (allowPrefix && na.length >= 7 && nb.length >= 7 && (na.startsWith(nb) || nb.startsWith(na)))
|
|
32
|
+
return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
/** §1.3 — allow-class for the merge gate: ALLOW (or WARN only when opted in) + a continue-class action. */
|
|
36
|
+
function isAllowClass(receipt, allowWarnMerge) {
|
|
37
|
+
const dec = receipt.decision;
|
|
38
|
+
const decisionOk = dec === 'ALLOW' || (dec === 'WARN' && allowWarnMerge === true);
|
|
39
|
+
if (!decisionOk)
|
|
40
|
+
return false;
|
|
41
|
+
const ea = receipt.execution_action;
|
|
42
|
+
if (ea === undefined || ea === null || ea === '')
|
|
43
|
+
return true; // decision already allow-class, no action to contradict it
|
|
44
|
+
return ea === 'CONTINUE' || ea === 'CONTINUE_WITH_MONITORING';
|
|
45
|
+
}
|
|
46
|
+
/** Target/repo binding: the receipt's target must name this repository (exact or `repo:`/path/colon suffix). */
|
|
47
|
+
function targetMatches(targetId, repository) {
|
|
48
|
+
const t = String(targetId).toLowerCase();
|
|
49
|
+
const r = String(repository).toLowerCase();
|
|
50
|
+
return t === r || t === `repo:${r}` || t.endsWith(`:${r}`) || t.endsWith(`/${r}`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* §1.4 — the normative, first-match-wins gate decision. Pure; no I/O; deterministic.
|
|
54
|
+
*/
|
|
55
|
+
export function gateDecision(input) {
|
|
56
|
+
const rc = input.requiredContext || {};
|
|
57
|
+
const protection = rc.protection || { enforcement: 'UNKNOWN', admin_bypass_possible: true };
|
|
58
|
+
const enforcement_state = protection.enforcement;
|
|
59
|
+
const allowPending = input.allowPending ?? rc.allowPending ?? false;
|
|
60
|
+
const allowWarnMerge = input.allowWarnMerge ?? rc.allowWarnMerge ?? false;
|
|
61
|
+
const allowPrefix = input.allowPrefixCompare ?? rc.allowPrefixCompare ?? false;
|
|
62
|
+
const receipt = input.receipt;
|
|
63
|
+
const detail = {
|
|
64
|
+
prHeadSha: normSha(input.prHeadSha),
|
|
65
|
+
bound_head_sha: receipt ? normSha(receipt.bound_head_sha) : null,
|
|
66
|
+
decision: receipt ? String(receipt.decision) : null,
|
|
67
|
+
};
|
|
68
|
+
const fail = (state, reason) => ({
|
|
69
|
+
merge_allowed: false, state, reason, enforcement_state, inescapable_merge: false, detail,
|
|
70
|
+
});
|
|
71
|
+
// 1) incomplete head → pending/failure (fail-closed on missing evaluation input).
|
|
72
|
+
if (!input.prHeadSha || String(input.prHeadSha).trim() === '') {
|
|
73
|
+
return fail(allowPending ? 'pending' : 'failure', 'inputs_incomplete');
|
|
74
|
+
}
|
|
75
|
+
// 2) no receipt → fail-closed (M2).
|
|
76
|
+
if (receipt === null || receipt === undefined) {
|
|
77
|
+
return fail(allowPending ? 'pending' : 'failure', 'no_receipt');
|
|
78
|
+
}
|
|
79
|
+
// 3) lifecycle authorization — a valid signature alone is NOT sufficient (M3).
|
|
80
|
+
if (receipt.currently_authorized !== true) {
|
|
81
|
+
return fail('failure', 'receipt_not_authorized');
|
|
82
|
+
}
|
|
83
|
+
// 4) operation binding — a receipt for a different operation does not authorize this gate.
|
|
84
|
+
const op = rc.operation ?? 'merge';
|
|
85
|
+
if (receipt.operation != null && receipt.operation !== op) {
|
|
86
|
+
return fail('failure', 'operation_mismatch');
|
|
87
|
+
}
|
|
88
|
+
// 5) STALE HEAD — the central invariant: a receipt bound to an old head never greens a new one (M1).
|
|
89
|
+
if (!sameHead(receipt.bound_head_sha, input.prHeadSha, allowPrefix)) {
|
|
90
|
+
return fail('failure', 'stale_head');
|
|
91
|
+
}
|
|
92
|
+
// 6) optional re-bind to the current change set.
|
|
93
|
+
if (rc.expected_fingerprint != null && receipt.verdict_fingerprint !== rc.expected_fingerprint) {
|
|
94
|
+
return fail('failure', 'fingerprint_mismatch');
|
|
95
|
+
}
|
|
96
|
+
if (rc.expected_body_hash != null && receipt.body_hash !== rc.expected_body_hash) {
|
|
97
|
+
return fail('failure', 'body_hash_mismatch');
|
|
98
|
+
}
|
|
99
|
+
// 7) target / repository binding (only when both sides are present).
|
|
100
|
+
if (rc.repository && receipt.target_id && !targetMatches(receipt.target_id, rc.repository)) {
|
|
101
|
+
return fail('failure', 'target_mismatch');
|
|
102
|
+
}
|
|
103
|
+
// 8) decision class — only an allow-class verdict greens the merge gate (M4).
|
|
104
|
+
if (!isAllowClass(receipt, allowWarnMerge)) {
|
|
105
|
+
return fail('failure', 'decision_not_allow');
|
|
106
|
+
}
|
|
107
|
+
// 9) success for the CHECK — green for visibility regardless of protection strength.
|
|
108
|
+
// 10) inescapable_merge claim (STRICT, §1.4 step 10 / M6): only when the platform actually enforces
|
|
109
|
+
// the required check AND no admin can bypass it. Never true otherwise.
|
|
110
|
+
const inescapable_merge = enforcement_state === 'ENFORCING' && protection.admin_bypass_possible === false;
|
|
111
|
+
let residual;
|
|
112
|
+
if (!inescapable_merge) {
|
|
113
|
+
if (enforcement_state === 'ENFORCING')
|
|
114
|
+
residual = 'admin_bypass_open'; // required, but admins can override
|
|
115
|
+
else if (enforcement_state === 'ADVISORY')
|
|
116
|
+
residual = 'protection_advisory_only'; // posted but not required
|
|
117
|
+
else
|
|
118
|
+
residual = 'protection_not_configured'; // ABSENT or UNKNOWN — cannot attest
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
merge_allowed: true,
|
|
122
|
+
state: 'success',
|
|
123
|
+
reason: 'allow_current_head',
|
|
124
|
+
enforcement_state,
|
|
125
|
+
inescapable_merge,
|
|
126
|
+
...(residual ? { residual } : {}),
|
|
127
|
+
detail,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=merge-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-gate.js","sourceRoot":"","sources":["../../src/merge-gate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderifts/agent-guard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Fail-closed guard for AI agent tool calls — preflight contract changes before they execute. Security core frozen (agent-guard-api v1.0); v1.1 adds client-side enforcement: receipt→envelope binding, decision↔action reconciliation, safe_for_agent + degraded fail-closed, and enforced⟺executed (P0 client-enforcement pack).",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|