coderifts 4.10.0 → 4.12.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/cli.js +1880 -231
- package/package.json +2 -2
- package/scripts/assert-guard-major.js +110 -63
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coderifts",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"description": "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
5
5
|
"author": "CodeRifts <hello@coderifts.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"test": "node --test test/*.test.js"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@coderifts/agent-guard": "^
|
|
48
|
+
"@coderifts/agent-guard": "^10.0.0",
|
|
49
49
|
"chalk": "^4.1.2",
|
|
50
50
|
"cli-table3": "^0.6.4",
|
|
51
51
|
"commander": "^12.0.0",
|
|
@@ -12,15 +12,63 @@
|
|
|
12
12
|
* Why: evening audit 2.4 — CLI sat on ^1.6.0 while published guard is 6.x.
|
|
13
13
|
* 4.0.0: floor raised to 8 (deployGate inescapable_deploy includes fingerprint rebound).
|
|
14
14
|
* 4.4.1: floor raised to 9 (TOKEN / VERIFIED-VIEW; bare currently_authorized is unverified_receipt_view).
|
|
15
|
+
* 4.12.0: floor raised to 10 (ENFORCING_STRICT requires the execution chain — a strict
|
|
16
|
+
* composition that built under 9.x refuses to construct under 10.x, so a CLI that documents or
|
|
17
|
+
* scaffolds strict must not run against a guard with the older meaning).
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
const fs = require('fs');
|
|
18
21
|
const path = require('path');
|
|
19
22
|
|
|
20
|
-
/** Minimum acceptable major for @coderifts/agent-guard (
|
|
21
|
-
const MIN_GUARD_MAJOR =
|
|
23
|
+
/** Minimum acceptable major for @coderifts/agent-guard in THIS package (the CLI). */
|
|
24
|
+
const MIN_GUARD_MAJOR = 10;
|
|
22
25
|
const DEP = '@coderifts/agent-guard';
|
|
23
26
|
|
|
27
|
+
/**
|
|
28
|
+
* EVERY checkout that declares the guard — not just this one (roadmap 1058).
|
|
29
|
+
*
|
|
30
|
+
* WHY THIS TABLE EXISTS: this gate checked packages/cli only, while the app ROOT — where
|
|
31
|
+
* PRODUCTION uses the guard (src/mergegate/webhook-integration.js imports gateDecision) — sat on
|
|
32
|
+
* ^3.0.0 against a published 10.0.0. A seven-major gap survived precisely because the gate did not
|
|
33
|
+
* look there. A gate that inspects one of two call sites is not a gate.
|
|
34
|
+
*
|
|
35
|
+
* ONE FILE, PER-CHECKOUT FLOORS — chosen over a separate sibling check so there is ONE history
|
|
36
|
+
* section to record the next raise in. Two files would mean two floors with no structural reason
|
|
37
|
+
* they are ever raised together, which is the drift that produced this finding.
|
|
38
|
+
*
|
|
39
|
+
* Floors differ ON PURPOSE. A single shared floor would fail immediately and truthfully, but the
|
|
40
|
+
* fix for that is a dependency bump this gate must not force: see the app-root entry.
|
|
41
|
+
*/
|
|
42
|
+
const CHECKOUTS = [
|
|
43
|
+
{
|
|
44
|
+
id: 'cli',
|
|
45
|
+
label: 'packages/cli',
|
|
46
|
+
pkgPath: path.join(__dirname, '..', 'package.json'),
|
|
47
|
+
floor: MIN_GUARD_MAJOR,
|
|
48
|
+
why: 'CLI floor history is recorded in the module docblock above.',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'app-root',
|
|
52
|
+
label: 'coderifts-app (root)',
|
|
53
|
+
pkgPath: path.join(__dirname, '..', '..', '..', 'package.json'),
|
|
54
|
+
floor: 3,
|
|
55
|
+
/**
|
|
56
|
+
* DELIBERATE, MEASURED, AND LOWER THAN THE CLI'S — recorded so it is a decision, not a default.
|
|
57
|
+
*
|
|
58
|
+
* 2026-08-26: bumping the root to ^10 breaks three merge-gate tests. gateDecision under 10.0.0
|
|
59
|
+
* returns residuals: [] where the app expects 'required_check_app_not_bound'
|
|
60
|
+
* (test/mergegate-webhook.test.js: 46/46 on 3.0.0, 3 failures on 10.0.0). The app's own comment
|
|
61
|
+
* at webhook-integration.js:336-339 records why 3.x has been SAFE rather than merely unnoticed:
|
|
62
|
+
* the app ANDs three conditions so a stale package can never fail-open.
|
|
63
|
+
*
|
|
64
|
+
* So the floor is pinned at 3 to stop it drifting LOWER unnoticed, and raising it is a separate
|
|
65
|
+
* round that must first reconcile the residual vocabulary. Raise this number in that round and
|
|
66
|
+
* record the reason on the line below, in the style of the CLI history above.
|
|
67
|
+
*/
|
|
68
|
+
why: 'root pinned at 3 pending the gateDecision residual-vocabulary reconciliation (see comment).',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
24
72
|
function fail(msg) {
|
|
25
73
|
console.error(`assert-guard-major: FAIL — ${msg}`);
|
|
26
74
|
process.exit(1);
|
|
@@ -47,81 +95,80 @@ function floorMajorFromRange(range) {
|
|
|
47
95
|
return parseInt(m[1], 10);
|
|
48
96
|
}
|
|
49
97
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
/** Resolve the installed guard version for a checkout dir, or null. */
|
|
99
|
+
function resolvedVersionFor(startDir) {
|
|
100
|
+
try {
|
|
101
|
+
let dir = path.dirname(require.resolve(DEP, { paths: [startDir] }));
|
|
102
|
+
for (let i = 0; i < 6; i += 1) {
|
|
103
|
+
const cand = path.join(dir, 'package.json');
|
|
104
|
+
if (fs.existsSync(cand)) {
|
|
105
|
+
const rp = JSON.parse(fs.readFileSync(cand, 'utf8'));
|
|
106
|
+
if (rp.name === DEP) return rp.version;
|
|
107
|
+
}
|
|
108
|
+
const parent = path.dirname(dir);
|
|
109
|
+
if (parent === dir) break;
|
|
110
|
+
dir = parent;
|
|
111
|
+
}
|
|
112
|
+
} catch (_) { /* not installed here */ }
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Check ONE checkout. Returns a line for the summary; calls fail() and exits on a violation. */
|
|
117
|
+
function checkCheckout(c) {
|
|
118
|
+
if (!fs.existsSync(c.pkgPath)) {
|
|
119
|
+
fail(`${c.label}: package.json not found at ${c.pkgPath}`);
|
|
120
|
+
}
|
|
121
|
+
const pkg = JSON.parse(fs.readFileSync(c.pkgPath, 'utf8'));
|
|
53
122
|
const range = pkg.dependencies && pkg.dependencies[DEP];
|
|
54
|
-
if (!range) fail(`${DEP} missing from dependencies`);
|
|
123
|
+
if (!range) fail(`${c.label}: ${DEP} missing from dependencies`);
|
|
55
124
|
|
|
56
125
|
const floor = floorMajorFromRange(range);
|
|
57
126
|
if (floor == null) {
|
|
58
127
|
fail(
|
|
59
|
-
`${DEP} range ${JSON.stringify(range)} has no parseable major floor `
|
|
60
|
-
+ `(wildcards/non-semver not allowed; need e.g. ^${
|
|
128
|
+
`${c.label}: ${DEP} range ${JSON.stringify(range)} has no parseable major floor `
|
|
129
|
+
+ `(wildcards/non-semver not allowed; need e.g. ^${c.floor}.0.0)`,
|
|
61
130
|
);
|
|
62
131
|
}
|
|
63
|
-
if (floor <
|
|
132
|
+
if (floor < c.floor) {
|
|
64
133
|
fail(
|
|
65
|
-
`${DEP} range ${JSON.stringify(range)} floors at major ${floor}; `
|
|
66
|
-
+ `minimum acceptable major is ${
|
|
134
|
+
`${c.label}: ${DEP} range ${JSON.stringify(range)} floors at major ${floor}; `
|
|
135
|
+
+ `minimum acceptable major for this checkout is ${c.floor} — ${c.why}`,
|
|
67
136
|
);
|
|
68
137
|
}
|
|
69
138
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
path.dirname(require.resolve(`${DEP}`)),
|
|
74
|
-
// package root: require('@coderifts/agent-guard') → dist/cjs/index.js
|
|
75
|
-
'..',
|
|
76
|
-
'..',
|
|
77
|
-
'package.json',
|
|
78
|
-
));
|
|
79
|
-
// From dist/cjs → package root is ../..
|
|
80
|
-
} catch (_) {
|
|
81
|
-
// try alternate resolve
|
|
139
|
+
const resolved = resolvedVersionFor(path.dirname(c.pkgPath));
|
|
140
|
+
if (!resolved) {
|
|
141
|
+
return `${c.label}: declared ${range} (floor ${floor} >= ${c.floor}); not resolved in tree`;
|
|
82
142
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
let dir = path.dirname(require.resolve(DEP));
|
|
88
|
-
for (let i = 0; i < 6; i++) {
|
|
89
|
-
const cand = path.join(dir, 'package.json');
|
|
90
|
-
if (fs.existsSync(cand)) {
|
|
91
|
-
const rp = JSON.parse(fs.readFileSync(cand, 'utf8'));
|
|
92
|
-
if (rp.name === DEP) {
|
|
93
|
-
resolvedVersion = rp.version;
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const parent = path.dirname(dir);
|
|
98
|
-
if (parent === dir) break;
|
|
99
|
-
dir = parent;
|
|
100
|
-
}
|
|
101
|
-
} catch (err) {
|
|
102
|
-
// Not installed — range check alone is enough for prepublish if install follows.
|
|
103
|
-
ok(`declared ${DEP}@${range} (floor major ${floor} ≥ ${MIN_GUARD_MAJOR}); package not resolved in tree (${err && err.message})`);
|
|
104
|
-
return;
|
|
143
|
+
const rm = /^(\d+)/.exec(resolved);
|
|
144
|
+
const rMajor = rm ? parseInt(rm[1], 10) : null;
|
|
145
|
+
if (rMajor == null || rMajor < c.floor) {
|
|
146
|
+
fail(`${c.label}: resolved ${DEP}@${resolved} major ${rMajor} is below minimum ${c.floor}`);
|
|
105
147
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const rm = /^(\d+)/.exec(resolvedVersion);
|
|
109
|
-
const rMajor = rm ? parseInt(rm[1], 10) : null;
|
|
110
|
-
if (rMajor == null || rMajor < MIN_GUARD_MAJOR) {
|
|
111
|
-
fail(
|
|
112
|
-
`resolved ${DEP}@${resolvedVersion} major ${rMajor} is below minimum ${MIN_GUARD_MAJOR}`,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
if (rMajor < floor) {
|
|
116
|
-
fail(
|
|
117
|
-
`resolved ${DEP}@${resolvedVersion} major ${rMajor} is below declared range floor ${floor}`,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
ok(`declared ${DEP}@${range} (floor ${floor}); resolved ${resolvedVersion} (major ≥ ${MIN_GUARD_MAJOR})`);
|
|
121
|
-
return;
|
|
148
|
+
if (rMajor < floor) {
|
|
149
|
+
fail(`${c.label}: resolved ${DEP}@${resolved} major ${rMajor} is below declared range floor ${floor}`);
|
|
122
150
|
}
|
|
151
|
+
return `${c.label}: declared ${range} (floor ${floor} >= ${c.floor}); resolved ${resolved}`;
|
|
152
|
+
}
|
|
123
153
|
|
|
124
|
-
|
|
154
|
+
function main() {
|
|
155
|
+
const lines = CHECKOUTS.map(checkCheckout);
|
|
156
|
+
for (const l of lines) ok(l);
|
|
157
|
+
|
|
158
|
+
// DIVERGENCE NOTICE — visible, never fatal. The floors differ deliberately (see CHECKOUTS), so
|
|
159
|
+
// failing here would block a publish over a state we chose. Printing it keeps the gap from
|
|
160
|
+
// going quiet again, which is how it survived seven majors.
|
|
161
|
+
const floors = CHECKOUTS.map((c) => c.floor);
|
|
162
|
+
if (new Set(floors).size > 1) {
|
|
163
|
+
console.log(
|
|
164
|
+
`assert-guard-major: NOTE — checkout floors differ (${CHECKOUTS.map((c) => `${c.id}=${c.floor}`).join(', ')}). `
|
|
165
|
+
+ 'Deliberate; each reason is recorded in CHECKOUTS. Not a failure.',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
125
168
|
}
|
|
126
169
|
|
|
127
|
-
|
|
170
|
+
// Run only when executed directly, so the gate's own tests can import CHECKOUTS /
|
|
171
|
+
// floorMajorFromRange without triggering a process.exit.
|
|
172
|
+
if (require.main === module) main();
|
|
173
|
+
|
|
174
|
+
module.exports = { CHECKOUTS, floorMajorFromRange, MIN_GUARD_MAJOR, DEP };
|