coderifts 5.0.1 → 6.0.1
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/CHANGELOG.md +104 -1
- package/dist/cli.js +410 -38
- package/package.json +3 -3
- package/scripts/assert-changelog-version.js +88 -0
- package/scripts/assert-guard-major.js +21 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 6.0.1
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- **Guard floor raised to 13, declared range `^13.0.0`.** ADOPTER-FACING: the guard is a direct
|
|
7
|
+
dependency with no `peerDependencies` and no bundled copy, so this range *is* what a fresh install
|
|
8
|
+
resolves. `coderifts@5.0.1` declared `^12.0.0`, which resolves to 12.0.0 — the version in which
|
|
9
|
+
`wrapWriteWithFsCas` measured its expected CAS token INSIDE `executeFactory`, after preflight and
|
|
10
|
+
after the T2 recheck, and conditioned the write on that self-fetched value. Measured on 12.0.0: a
|
|
11
|
+
writer landing in that window had its state adopted as the legitimate starting point and the CAS
|
|
12
|
+
still returned `committed` with `enforced: true`, clobbering it. 13.0.0 threads the
|
|
13
|
+
authorization-time token, and adds three filesystem defences that were measured absent rather than
|
|
14
|
+
assumed — `..` refused before resolution, a symlinked final component refused (`stat`/`readFile`
|
|
15
|
+
measured the link's TARGET while `rename` replaced the LINK, so check and write named different
|
|
16
|
+
objects), the token read through a single file descriptor, and `dev`+`ino` recorded in the write
|
|
17
|
+
evidence.
|
|
18
|
+
|
|
19
|
+
**Cost measured as zero**, the same way the 11-, 12- and 13-raises were: the CLI imports only
|
|
20
|
+
`deployGate`, `asVerifiedDeployReceiptView`, `DEPLOY_RECEIPT_VIEW_SPEC` and `matchGlob`, whose
|
|
21
|
+
implementing modules are byte-identical 12.0.0 → 13.0.0, and all four return identical values
|
|
22
|
+
under both. Suite staged against 13.0.0 returned an identical 539/518/21 with a byte-identical
|
|
23
|
+
28-item failure set. Nothing in the CLI calls the CAS or FS adapters.
|
|
24
|
+
|
|
25
|
+
## 5.0.1
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **A fresh install shipped the guard with the conditional-write hole.**
|
|
29
|
+
`coderifts@5.0.0` declared `@coderifts/agent-guard: ^11.0.0`, which resolved to
|
|
30
|
+
11.0.1 — precisely the tarball the guard's own 12.0.0 round reproduced the
|
|
31
|
+
defect against, where `requireConditionalWrite` did not fire on an ordinary
|
|
32
|
+
mutation and the side effect **ran** with `enforced: true`. The guard is a
|
|
33
|
+
**direct** dependency with no `peerDependencies` and no bundled copy, so this
|
|
34
|
+
range *is* the adopter's resolution, not advice. Now `^12.0.0`, with
|
|
35
|
+
`MIN_GUARD_MAJOR` raised to 12. Cost measured as zero: the CLI imports only
|
|
36
|
+
`deployGate`, `asVerifiedDeployReceiptView`, `DEPLOY_RECEIPT_VIEW_SPEC` and
|
|
37
|
+
`matchGlob`, whose modules are byte-identical 11.0.1 → 12.0.0.
|
|
38
|
+
- **Two defects in the publish gate itself**, both proven by replaying the real
|
|
39
|
+
script. The resolved check only ever compared *downward* — `resolved >= floor`,
|
|
40
|
+
never whether the resolved version sits **inside** the declared range — so
|
|
41
|
+
declaring `^11.0.0` with 12.0.0 staged for a cost measurement printed OK and
|
|
42
|
+
exited 0, which is exactly the shape that makes "we measured 12 and shipped
|
|
43
|
+
^11" indistinguishable from a correct release. And an unresolvable install was
|
|
44
|
+
a **pass**. Three failure modes added (resolved below the declared range at
|
|
45
|
+
full precision, resolved above it, and not resolvable at all), with no
|
|
46
|
+
opt-out — an `allowUnresolved` flag was written and deliberately removed, and
|
|
47
|
+
a test asserts it stays absent.
|
|
48
|
+
|
|
49
|
+
## 5.0.0
|
|
4
50
|
|
|
5
51
|
### Fixed
|
|
6
52
|
- **P0 — `setup-required-check --apply` no longer reports `APPLIED` over a
|
|
@@ -59,6 +105,63 @@
|
|
|
59
105
|
template is unchanged and stays non-blocking; it carries the same three lines
|
|
60
106
|
commented, with a note saying why the two differ.
|
|
61
107
|
|
|
108
|
+
## 4.13.0 — never published (folded into 5.0.0)
|
|
109
|
+
|
|
110
|
+
Bumped in-repo (`917611d`) but never tagged or released to npm; 5.0.0 superseded
|
|
111
|
+
it, so adopters received this content **as part of 5.0.0**. Recorded separately
|
|
112
|
+
because the version number appears in the source history and in
|
|
113
|
+
`assert-guard-major.js`'s floor-raise log.
|
|
114
|
+
|
|
115
|
+
### Changed
|
|
116
|
+
- **Guard floor raised to 11.** Cost measured as zero the way the 10-raise was —
|
|
117
|
+
the CLI imports only `deployGate`, `asVerifiedDeployReceiptView`,
|
|
118
|
+
`DEPLOY_RECEIPT_VIEW_SPEC` and `matchGlob`, none of which 11.0.0 touched, and
|
|
119
|
+
the suite staged against 11 returned an identical 532/530/2 with a
|
|
120
|
+
byte-identical failure set. It rises anyway, because a semantics floor is about
|
|
121
|
+
what the CLI may **run** against, not what it happens to import today: 10.x's
|
|
122
|
+
`computeBundleFingerprint` disagreed with the server, which is exactly the
|
|
123
|
+
class this gate exists to refuse.
|
|
124
|
+
- The two tests pinning the old floor were updated, and the invariant made
|
|
125
|
+
explicit: **the floor only ever rises**.
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
- `/api/v1/agent/preflight` returns `scorer_version` — the one field that makes
|
|
129
|
+
the single-spec fingerprint reproducible by a consumer holding their own
|
|
130
|
+
before/after/policy. Purely additive; no surface moves and `tools_sha256` is
|
|
131
|
+
unchanged.
|
|
132
|
+
|
|
133
|
+
## 4.12.0
|
|
134
|
+
|
|
135
|
+
### Changed
|
|
136
|
+
- **Guard floor raised to 10.** Cost measured as zero: the CLI imports only
|
|
137
|
+
`deployGate`, `asVerifiedDeployReceiptView`, `DEPLOY_RECEIPT_VIEW_SPEC` and
|
|
138
|
+
`matchGlob` — never `withCodeRifts` — so 10.0.0's strict-composition break
|
|
139
|
+
cannot reach it. Verified by staging guard 10 into the install and running the
|
|
140
|
+
suite (guard-conformance 6/6, the same two pre-existing failures as the guard-9
|
|
141
|
+
baseline). The gate was satisfied, not weakened: before the install it
|
|
142
|
+
correctly reported FAIL on the stale resolve.
|
|
143
|
+
|
|
144
|
+
## 4.11.0
|
|
145
|
+
|
|
146
|
+
### Fixed
|
|
147
|
+
- **P0 — the issuer binding pointed at a check that cannot block.** GitHub passes
|
|
148
|
+
a required status check on `success`, `skipped` **or `neutral`**, so our App's
|
|
149
|
+
advisory check genuinely cannot block; a false comment in
|
|
150
|
+
`webhook-integration.js` claiming otherwise is what produced the regression.
|
|
151
|
+
The enforcing issuer is `app_id 15368` (github-actions), which the contract-gate
|
|
152
|
+
Action posts under, so that binding is now VERIFIED — with the shared-identity
|
|
153
|
+
residual **named** rather than implied: anyone who can edit
|
|
154
|
+
`.github/workflows` can post under it. A binding to our own App id grades
|
|
155
|
+
UNVERIFIABLE rather than NOT_VERIFIED, because `MERGEGATE_ENFORCE` is
|
|
156
|
+
server-side and invisible from here.
|
|
157
|
+
- **P0 — the installer wrote what our own verifier rejects.** It now writes
|
|
158
|
+
`checks[]` with an explicit `app_id`, because an omitted `app_id` silently
|
|
159
|
+
resolves to whoever posted last, which is unauditable. It never re-binds
|
|
160
|
+
another team's existing check, and the success path prints what it does **not**
|
|
161
|
+
achieve. A coupling test asserts the installer's and the verifier's issuer
|
|
162
|
+
constants are equal. The verifier was not weakened — the name-only and
|
|
163
|
+
legacy-contexts paths are byte-identical, asserted by two unchanged tests.
|
|
164
|
+
|
|
62
165
|
## 4.10.0
|
|
63
166
|
|
|
64
167
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -3028,7 +3028,7 @@ var require_package = __commonJS({
|
|
|
3028
3028
|
"package.json"(exports2, module2) {
|
|
3029
3029
|
module2.exports = {
|
|
3030
3030
|
name: "coderifts",
|
|
3031
|
-
version: "
|
|
3031
|
+
version: "6.0.1",
|
|
3032
3032
|
description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
3033
3033
|
author: "CodeRifts <hello@coderifts.com>",
|
|
3034
3034
|
license: "MIT",
|
|
@@ -3067,13 +3067,13 @@ var require_package = __commonJS({
|
|
|
3067
3067
|
},
|
|
3068
3068
|
scripts: {
|
|
3069
3069
|
build: "node scripts/copy-corpus.js && node scripts/build.js",
|
|
3070
|
-
prepublishOnly: "node scripts/assert-guard-major.js && bash ../../scripts/freeze-gate.sh && npm run build",
|
|
3070
|
+
prepublishOnly: "node scripts/assert-guard-major.js && node scripts/assert-changelog-version.js && bash ../../scripts/freeze-gate.sh && npm run build",
|
|
3071
3071
|
"assert-guard-major": "node scripts/assert-guard-major.js",
|
|
3072
3072
|
postinstall: "node scripts/postinstall.js",
|
|
3073
3073
|
test: "node --test test/*.test.js"
|
|
3074
3074
|
},
|
|
3075
3075
|
dependencies: {
|
|
3076
|
-
"@coderifts/agent-guard": "^
|
|
3076
|
+
"@coderifts/agent-guard": "^13.0.0",
|
|
3077
3077
|
chalk: "^4.1.2",
|
|
3078
3078
|
"cli-table3": "^0.6.4",
|
|
3079
3079
|
commander: "^12.0.0",
|
|
@@ -67189,6 +67189,9 @@ var require_conditional_write = __commonJS({
|
|
|
67189
67189
|
write_style,
|
|
67190
67190
|
mutating
|
|
67191
67191
|
};
|
|
67192
|
+
if (ctx.guarantee === "SAME_TRANSACTION" || ctx.guarantee === "CONDITIONAL_EXTERNAL" || ctx.guarantee === "NON_ATOMIC") {
|
|
67193
|
+
basis.guarantee = ctx.guarantee;
|
|
67194
|
+
}
|
|
67192
67195
|
if (conditional_write === true) {
|
|
67193
67196
|
if (typeof ctx.conditioned_on_token === "string") {
|
|
67194
67197
|
basis.conditioned_on_token = ctx.conditioned_on_token;
|
|
@@ -67324,8 +67327,11 @@ var require_fs = __commonJS({
|
|
|
67324
67327
|
};
|
|
67325
67328
|
})();
|
|
67326
67329
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
67327
|
-
exports2.tokensEqual = exports2.FS_ABSENT_TOKEN = exports2.FS_VERSION_TOKEN_PREFIX = void 0;
|
|
67330
|
+
exports2.tokensEqual = exports2.UnsafeCasPathError = exports2.FS_ABSENT_TOKEN = exports2.FS_VERSION_TOKEN_PREFIX = void 0;
|
|
67328
67331
|
exports2.fsTokenContentHash = fsTokenContentHash;
|
|
67332
|
+
exports2.assertSafeCasPath = assertSafeCasPath;
|
|
67333
|
+
exports2.fsObjectIdentity = fsObjectIdentity;
|
|
67334
|
+
exports2.identitiesEqual = identitiesEqual;
|
|
67329
67335
|
exports2.createFsVersionToken = createFsVersionToken;
|
|
67330
67336
|
exports2.readVersionedFile = readVersionedFile;
|
|
67331
67337
|
exports2.writeFileIfUnchanged = writeFileIfUnchanged;
|
|
@@ -67354,23 +67360,90 @@ var require_fs = __commonJS({
|
|
|
67354
67360
|
const hash = rest.slice(colon + 1);
|
|
67355
67361
|
return /^[a-f0-9]{64}$/.test(hash) ? hash : null;
|
|
67356
67362
|
}
|
|
67363
|
+
var UnsafeCasPathError = class extends Error {
|
|
67364
|
+
reason;
|
|
67365
|
+
casPath;
|
|
67366
|
+
constructor(reason, casPath) {
|
|
67367
|
+
super(`unsafe CAS path (${reason}): ${casPath}`);
|
|
67368
|
+
this.name = "UnsafeCasPathError";
|
|
67369
|
+
this.reason = reason;
|
|
67370
|
+
this.casPath = casPath;
|
|
67371
|
+
}
|
|
67372
|
+
};
|
|
67373
|
+
exports2.UnsafeCasPathError = UnsafeCasPathError;
|
|
67374
|
+
async function assertSafeCasPath(filePath) {
|
|
67375
|
+
const segments = filePath.split(/[\\/]+/);
|
|
67376
|
+
if (segments.includes(".."))
|
|
67377
|
+
throw new UnsafeCasPathError("path_traversal", filePath);
|
|
67378
|
+
const resolved = path.resolve(filePath);
|
|
67379
|
+
const dir = path.dirname(resolved);
|
|
67380
|
+
let canonicalDir = dir;
|
|
67381
|
+
try {
|
|
67382
|
+
canonicalDir = await node_fs_1.promises.realpath(dir);
|
|
67383
|
+
} catch (err) {
|
|
67384
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
|
|
67385
|
+
if (code !== "ENOENT")
|
|
67386
|
+
throw err;
|
|
67387
|
+
}
|
|
67388
|
+
const target = path.join(canonicalDir, path.basename(resolved));
|
|
67389
|
+
try {
|
|
67390
|
+
const lst = await node_fs_1.promises.lstat(target);
|
|
67391
|
+
if (lst.isSymbolicLink())
|
|
67392
|
+
throw new UnsafeCasPathError("symlink_target", filePath);
|
|
67393
|
+
} catch (err) {
|
|
67394
|
+
if (err instanceof UnsafeCasPathError)
|
|
67395
|
+
throw err;
|
|
67396
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
|
|
67397
|
+
if (code !== "ENOENT")
|
|
67398
|
+
throw err;
|
|
67399
|
+
}
|
|
67400
|
+
return target;
|
|
67401
|
+
}
|
|
67402
|
+
async function fsObjectIdentity(filePath) {
|
|
67403
|
+
let fh;
|
|
67404
|
+
try {
|
|
67405
|
+
fh = await node_fs_1.promises.open(filePath, "r");
|
|
67406
|
+
const st = await fh.stat();
|
|
67407
|
+
return { dev: st.dev, ino: st.ino };
|
|
67408
|
+
} catch (err) {
|
|
67409
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
|
|
67410
|
+
if (code === "ENOENT")
|
|
67411
|
+
return null;
|
|
67412
|
+
throw err;
|
|
67413
|
+
} finally {
|
|
67414
|
+
if (fh)
|
|
67415
|
+
await fh.close().catch(() => {
|
|
67416
|
+
});
|
|
67417
|
+
}
|
|
67418
|
+
}
|
|
67419
|
+
function identitiesEqual(a, b) {
|
|
67420
|
+
if (!a || !b)
|
|
67421
|
+
return false;
|
|
67422
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
67423
|
+
}
|
|
67357
67424
|
async function createFsVersionToken(filePath) {
|
|
67358
|
-
let
|
|
67425
|
+
let fh;
|
|
67359
67426
|
try {
|
|
67360
|
-
|
|
67427
|
+
fh = await node_fs_1.promises.open(filePath, "r");
|
|
67361
67428
|
} catch (err) {
|
|
67362
67429
|
const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
|
|
67363
67430
|
if (code === "ENOENT")
|
|
67364
67431
|
return exports2.FS_ABSENT_TOKEN;
|
|
67365
67432
|
throw err;
|
|
67366
67433
|
}
|
|
67367
|
-
|
|
67368
|
-
|
|
67434
|
+
try {
|
|
67435
|
+
const st = await fh.stat();
|
|
67436
|
+
if (!st.isFile()) {
|
|
67437
|
+
throw new Error(`createFsVersionToken: not a regular file: ${filePath}`);
|
|
67438
|
+
}
|
|
67439
|
+
const buf = await fh.readFile();
|
|
67440
|
+
const hash = sha256hex(buf);
|
|
67441
|
+
const mtimeMs = Math.trunc(st.mtimeMs);
|
|
67442
|
+
return `${exports2.FS_VERSION_TOKEN_PREFIX}${mtimeMs}:${hash}`;
|
|
67443
|
+
} finally {
|
|
67444
|
+
await fh.close().catch(() => {
|
|
67445
|
+
});
|
|
67369
67446
|
}
|
|
67370
|
-
const buf = await node_fs_1.promises.readFile(filePath);
|
|
67371
|
-
const hash = sha256hex(buf);
|
|
67372
|
-
const mtimeMs = Math.trunc(st.mtimeMs);
|
|
67373
|
-
return `${exports2.FS_VERSION_TOKEN_PREFIX}${mtimeMs}:${hash}`;
|
|
67374
67447
|
}
|
|
67375
67448
|
async function readVersionedFile(filePath) {
|
|
67376
67449
|
const version_token = await createFsVersionToken(filePath);
|
|
@@ -67381,9 +67454,23 @@ var require_fs = __commonJS({
|
|
|
67381
67454
|
return { content, version_token };
|
|
67382
67455
|
}
|
|
67383
67456
|
async function writeFileIfUnchanged(args) {
|
|
67384
|
-
const target =
|
|
67457
|
+
const target = await assertSafeCasPath(args.path);
|
|
67385
67458
|
const body = typeof args.content === "string" ? Buffer.from(args.content, "utf8") : args.content;
|
|
67386
67459
|
const writtenHash = sha256hex(body);
|
|
67460
|
+
const checkedIdentity = await fsObjectIdentity(target);
|
|
67461
|
+
const hasT1Identity = Object.prototype.hasOwnProperty.call(args, "expected_identity");
|
|
67462
|
+
if (hasT1Identity) {
|
|
67463
|
+
const t1 = args.expected_identity ?? null;
|
|
67464
|
+
const held = t1 === null ? checkedIdentity === null : identitiesEqual(t1, checkedIdentity);
|
|
67465
|
+
if (!held) {
|
|
67466
|
+
return {
|
|
67467
|
+
status: "refused",
|
|
67468
|
+
reason: "stale_version_token",
|
|
67469
|
+
expected_token: args.expected_token,
|
|
67470
|
+
current_token: await createFsVersionToken(target).catch(() => null)
|
|
67471
|
+
};
|
|
67472
|
+
}
|
|
67473
|
+
}
|
|
67387
67474
|
return (0, conditional_write_js_1.executeIfUnchanged)({
|
|
67388
67475
|
expected_token: args.expected_token,
|
|
67389
67476
|
current_token: () => createFsVersionToken(target),
|
|
@@ -67402,6 +67489,15 @@ var require_fs = __commonJS({
|
|
|
67402
67489
|
const tmp = path.join(dir, `.coderifts-cas-${path.basename(target)}-${process.pid}-${(0, node_crypto_1.randomBytes)(6).toString("hex")}.tmp`);
|
|
67403
67490
|
try {
|
|
67404
67491
|
await node_fs_1.promises.writeFile(tmp, body);
|
|
67492
|
+
const stillIdentity = await fsObjectIdentity(target);
|
|
67493
|
+
const identityHeld = checkedIdentity === null ? stillIdentity === null : identitiesEqual(checkedIdentity, stillIdentity);
|
|
67494
|
+
if (!identityHeld) {
|
|
67495
|
+
try {
|
|
67496
|
+
await node_fs_1.promises.unlink(tmp);
|
|
67497
|
+
} catch {
|
|
67498
|
+
}
|
|
67499
|
+
throw new conditional_write_js_1.StaleVersionTokenAbort(await createFsVersionToken(target).catch(() => null));
|
|
67500
|
+
}
|
|
67405
67501
|
const still = await createFsVersionToken(target);
|
|
67406
67502
|
if (!(0, conditional_write_js_1.tokensEqual)(args.expected_token, still)) {
|
|
67407
67503
|
try {
|
|
@@ -67420,7 +67516,13 @@ var require_fs = __commonJS({
|
|
|
67420
67516
|
}
|
|
67421
67517
|
throw err;
|
|
67422
67518
|
}
|
|
67423
|
-
return {
|
|
67519
|
+
return {
|
|
67520
|
+
path: target,
|
|
67521
|
+
bytes: body.length,
|
|
67522
|
+
written_content_hash: writtenHash,
|
|
67523
|
+
checked_identity: checkedIdentity,
|
|
67524
|
+
committed_identity: await fsObjectIdentity(target)
|
|
67525
|
+
};
|
|
67424
67526
|
}
|
|
67425
67527
|
});
|
|
67426
67528
|
}
|
|
@@ -69409,6 +69511,10 @@ var require_cas_attestation = __commonJS({
|
|
|
69409
69511
|
const s = x;
|
|
69410
69512
|
return s.reason === "stale_during_commit" && typeof s.expected_token === "string";
|
|
69411
69513
|
}
|
|
69514
|
+
if (o.status === "indeterminate") {
|
|
69515
|
+
const i = x;
|
|
69516
|
+
return (i.reason === "response_lost" || i.reason === "ambiguous_provider_reply" || i.reason === "observation_failed") && typeof i.expected_token === "string";
|
|
69517
|
+
}
|
|
69412
69518
|
return false;
|
|
69413
69519
|
}
|
|
69414
69520
|
function freezeExecutionResultHash(h) {
|
|
@@ -69431,6 +69537,15 @@ var require_cas_attestation = __commonJS({
|
|
|
69431
69537
|
current_token: outcome.current_token == null ? null : outcome.current_token
|
|
69432
69538
|
});
|
|
69433
69539
|
}
|
|
69540
|
+
if (outcome.status === "indeterminate") {
|
|
69541
|
+
return Object.freeze({
|
|
69542
|
+
status: "indeterminate",
|
|
69543
|
+
write_ran: "unknown",
|
|
69544
|
+
reason: outcome.reason,
|
|
69545
|
+
expected_token: outcome.expected_token,
|
|
69546
|
+
observed_token: outcome.observed_token == null ? null : outcome.observed_token
|
|
69547
|
+
});
|
|
69548
|
+
}
|
|
69434
69549
|
return Object.freeze({
|
|
69435
69550
|
status: "committed_stale_detected",
|
|
69436
69551
|
write_ran: true,
|
|
@@ -69451,6 +69566,7 @@ var require_cas_attestation = __commonJS({
|
|
|
69451
69566
|
const write_ran = cas.write_ran === true;
|
|
69452
69567
|
const stale_during_commit = cas.status === "committed_stale_detected";
|
|
69453
69568
|
const refused = cas.status === "refused";
|
|
69569
|
+
const indeterminate = cas.status === "indeterminate";
|
|
69454
69570
|
const cas_evidence = evaluateCasEvidence(outcome, opts);
|
|
69455
69571
|
let authorized_and_committed = receipt_verified && cas.status === "committed";
|
|
69456
69572
|
if (opts.profile === "ENFORCING_STRICT") {
|
|
@@ -69471,7 +69587,8 @@ var require_cas_attestation = __commonJS({
|
|
|
69471
69587
|
authorized_and_committed,
|
|
69472
69588
|
write_ran,
|
|
69473
69589
|
stale_during_commit,
|
|
69474
|
-
refused
|
|
69590
|
+
refused,
|
|
69591
|
+
indeterminate
|
|
69475
69592
|
}),
|
|
69476
69593
|
cas_evidence,
|
|
69477
69594
|
limits: LIMITS
|
|
@@ -72729,6 +72846,8 @@ var require_fs_default_wire = __commonJS({
|
|
|
72729
72846
|
exports2.inferFsPathFromArgs = inferFsPathFromArgs;
|
|
72730
72847
|
exports2.inferFullFileWriteContent = inferFullFileWriteContent;
|
|
72731
72848
|
exports2.wrapWriteWithFsCas = wrapWriteWithFsCas;
|
|
72849
|
+
exports2.measureFsAuthorization = measureFsAuthorization;
|
|
72850
|
+
exports2.measureFsAuthorizationToken = measureFsAuthorizationToken;
|
|
72732
72851
|
var cas_attestation_js_1 = require_cas_attestation();
|
|
72733
72852
|
var fs_js_1 = require_fs();
|
|
72734
72853
|
function inferFsPathFromArgs(args) {
|
|
@@ -72755,20 +72874,16 @@ var require_fs_default_wire = __commonJS({
|
|
|
72755
72874
|
}
|
|
72756
72875
|
return null;
|
|
72757
72876
|
}
|
|
72758
|
-
async function wrapWriteWithFsCas(args, write) {
|
|
72877
|
+
async function wrapWriteWithFsCas(args, write, opts) {
|
|
72759
72878
|
const filePath = inferFsPathFromArgs(args);
|
|
72760
72879
|
const content = inferFullFileWriteContent(args);
|
|
72761
|
-
|
|
72762
|
-
|
|
72763
|
-
try {
|
|
72764
|
-
expected = await (0, fs_js_1.createFsVersionToken)(filePath);
|
|
72765
|
-
} catch {
|
|
72766
|
-
return write();
|
|
72767
|
-
}
|
|
72880
|
+
const expected = opts && typeof opts.expected_token === "string" ? opts.expected_token : null;
|
|
72881
|
+
if (filePath && content != null && expected !== null) {
|
|
72768
72882
|
return (0, fs_js_1.writeFileIfUnchanged)({
|
|
72769
72883
|
path: filePath,
|
|
72770
72884
|
expected_token: expected,
|
|
72771
|
-
content
|
|
72885
|
+
content,
|
|
72886
|
+
...opts && "expected_identity" in opts ? { expected_identity: opts.expected_identity ?? null } : {}
|
|
72772
72887
|
});
|
|
72773
72888
|
}
|
|
72774
72889
|
const raw = await write();
|
|
@@ -72776,6 +72891,24 @@ var require_fs_default_wire = __commonJS({
|
|
|
72776
72891
|
return raw;
|
|
72777
72892
|
return raw;
|
|
72778
72893
|
}
|
|
72894
|
+
async function measureFsAuthorization(args) {
|
|
72895
|
+
const filePath = inferFsPathFromArgs(args);
|
|
72896
|
+
const content = inferFullFileWriteContent(args);
|
|
72897
|
+
if (!filePath || content == null)
|
|
72898
|
+
return null;
|
|
72899
|
+
try {
|
|
72900
|
+
const target = await (0, fs_js_1.assertSafeCasPath)(filePath);
|
|
72901
|
+
const expected_identity = await (0, fs_js_1.fsObjectIdentity)(target);
|
|
72902
|
+
const expected_token = await (0, fs_js_1.createFsVersionToken)(target);
|
|
72903
|
+
return { expected_token, expected_identity };
|
|
72904
|
+
} catch {
|
|
72905
|
+
return null;
|
|
72906
|
+
}
|
|
72907
|
+
}
|
|
72908
|
+
async function measureFsAuthorizationToken(args) {
|
|
72909
|
+
const m = await measureFsAuthorization(args);
|
|
72910
|
+
return m ? m.expected_token : null;
|
|
72911
|
+
}
|
|
72779
72912
|
}
|
|
72780
72913
|
});
|
|
72781
72914
|
|
|
@@ -72960,6 +73093,11 @@ var require_tool_registry = __commonJS({
|
|
|
72960
73093
|
lastDerivation = d.derivation;
|
|
72961
73094
|
return d.call;
|
|
72962
73095
|
};
|
|
73096
|
+
const rebindAndRemeasure = async () => {
|
|
73097
|
+
const c = await rebind();
|
|
73098
|
+
casAuth = await (0, fs_default_wire_js_1.measureFsAuthorization)(args);
|
|
73099
|
+
return c;
|
|
73100
|
+
};
|
|
72963
73101
|
const call = await rebind();
|
|
72964
73102
|
const refreshContext = async (c) => (0, freshness_js_1.collectFreshnessCallContext)({
|
|
72965
73103
|
call: {
|
|
@@ -72970,16 +73108,17 @@ var require_tool_registry = __commonJS({
|
|
|
72970
73108
|
resolvePriorContent: guardCfg.resolvePriorContent
|
|
72971
73109
|
});
|
|
72972
73110
|
const fctx = await refreshContext(call);
|
|
73111
|
+
let casAuth = await (0, fs_default_wire_js_1.measureFsAuthorization)(args);
|
|
72973
73112
|
const factory = async (_envelope, redacted, execution) => {
|
|
72974
73113
|
const rawArgs = redacted ? redacted.arguments : args;
|
|
72975
|
-
return (0, fs_default_wire_js_1.wrapWriteWithFsCas)(rawArgs, () => execution ? rawExecute(rawArgs, execution) : rawExecute(rawArgs));
|
|
73114
|
+
return (0, fs_default_wire_js_1.wrapWriteWithFsCas)(rawArgs, () => execution ? rawExecute(rawArgs, execution) : rawExecute(rawArgs), casAuth ? { expected_token: casAuth.expected_token, expected_identity: casAuth.expected_identity } : {});
|
|
72976
73115
|
};
|
|
72977
73116
|
const outcome = (0, auto_recheck_js_1.normalizeAutoRecheck)(guardCfg.autoRecheck) ? await (0, auto_recheck_js_1.runAutoRecheckLoop)({
|
|
72978
73117
|
call,
|
|
72979
73118
|
factory,
|
|
72980
73119
|
config: guardCfg,
|
|
72981
73120
|
callContext: fctx,
|
|
72982
|
-
rebind,
|
|
73121
|
+
rebind: rebindAndRemeasure,
|
|
72983
73122
|
refreshContext
|
|
72984
73123
|
}) : await (0, guard_js_1.guardToolCall)(call, factory, guardCfg, fctx);
|
|
72985
73124
|
return (0, auto_derive_js_1.attachDerivation)(outcome, lastDerivation);
|
|
@@ -75298,9 +75437,10 @@ var require_cjs4 = __commonJS({
|
|
|
75298
75437
|
"use strict";
|
|
75299
75438
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
75300
75439
|
exports2.monitorAttestSigningInput = exports2.kidFromMonitoringAttestation = exports2.tryIssueMonitoringAttestation = exports2.DEFAULT_MONITORING_SINK_TIMEOUT_MS = exports2.ackBytes = exports2.verifyAckHmac = exports2.monitoringDeliveryFailClosed = exports2.formatMonitoringDeliveryLine = exports2.deliverMonitoring = exports2.hashObservedContent = exports2.observeCommit = exports2.deriveKeySignal = exports2.pathClass = exports2.classifyCommand = exports2.projectState = exports2.emptySessionState = exports2.computeTainted = exports2.evaluate = exports2.updateSession = exports2.SESSION_TAINT_VERSION = exports2.SessionTaintTracker = exports2.readDecision = exports2.EXECUTION_TIME_FP_REASONS = exports2.EXECUTION_STATE_UNMEASURABLE_NOTE = exports2.isUnmeasurableExecutionStateReason = exports2.computeCanonicalBundleFingerprint = exports2.authorizedFingerprintFromEnvelope = exports2.checkExecutionTimeFingerprint = exports2.computeBundleFingerprint = exports2.computeArtifactDigest = exports2.evaluateEnvelope = exports2.RECEIPT_PREV_NULL = exports2.decodeReceiptBodyPrev = exports2.previousReceiptCommitment = exports2.verifyReceiptChainLinkage = exports2.canonicalJson = exports2.computeBodyHash = exports2.bindReceiptToEnvelope = exports2.DETECTOR_VERSION = exports2.builtinDetector = exports2.resetPolicyWarnForTests = exports2.warnPolicyAbsentOnce = exports2.observePolicyPresence = exports2.detectPolicyPresence = exports2.policyPresenceOf = exports2.withPolicy = exports2.POLICY_ABSENT_WARN = exports2.POLICY_MARKER = exports2.CODERIFTS_POLICY = exports2.guardToolCall = void 0;
|
|
75301
|
-
exports2.
|
|
75302
|
-
exports2.
|
|
75303
|
-
exports2.
|
|
75440
|
+
exports2.createDbVersionToken = exports2.API_ABSENT_TOKEN = exports2.API_VERSION_TOKEN_PREFIX = exports2.apiTokenRaw = exports2.writeApiIfUnchanged = exports2.createApiVersionToken = exports2.identitiesEqual = exports2.fsObjectIdentity = exports2.UnsafeCasPathError = exports2.assertSafeCasPath = exports2.FS_ABSENT_TOKEN = exports2.FS_VERSION_TOKEN_PREFIX = exports2.fsTokenContentHash = exports2.createFsPriorContentResolver = exports2.writeFileIfUnchanged = exports2.readVersionedFile = exports2.createFsVersionToken = exports2.StaleVersionTokenAbort = exports2.executeIfUnchanged = exports2.RESIDUAL_UNCONDITIONAL_WRITE = exports2.conditionalWriteResidual = exports2.tokensEqual = exports2.buildConditionalWriteBasis = exports2.buildFreshnessBasis = exports2.collectFreshnessCallContext = exports2.artifactIdsForResolve = exports2.isMutatingCall = exports2.isWriteStyleCall = exports2.freshnessAllowsEnforce = exports2.computePathSetTreeHash = exports2.contentByteIdentical = exports2.assessWriteStylePrior = exports2.assessFreshness = exports2.deriveProofBanner = exports2.attachProofToAgentResponse = exports2.renderFinalAnswerProof = exports2.EXECUTION_PROOF_SPEC = exports2.assertEnforcedReceiptInvariant = exports2.hashExecutionResult = exports2.buildExecutionProof = exports2.COVERAGE_ATTEST_ENVELOPE_TAG = exports2.COVERAGE_ATTEST_SIGNING_PREFIX = exports2.COVERAGE_ATTEST_VERSION = exports2.coverageAttestSigningInput = exports2.kidFromCoverageAttestation = exports2.tryIssueCoverageAttestation = exports2.MONITOR_ATTEST_ENVELOPE_TAG = exports2.MONITOR_ATTEST_SIGNING_PREFIX = exports2.MONITOR_ATTEST_VERSION = exports2.receiptDigestOfToken = void 0;
|
|
75441
|
+
exports2.guardedFractionAmongRoutes = exports2.foldTableSettledCalls = exports2.withCodeRifts = exports2.AUTO_DERIVE_READ_TIMEOUT_MS = exports2.AUTO_DERIVE_SOURCE = exports2.defaultFsReader = exports2.normalizeAutoDerive = exports2.runAutoDerive = exports2.AUTO_RECHECK_MAX_CAP = exports2.clampMaxAttempts = exports2.normalizeAutoRecheck = exports2.runAutoRecheckLoop = exports2.coverageReport = exports2.DEPLOY_REPAIRABLE_REASONS = exports2.bindDeploy = exports2.verifyDeployReceiptToken = exports2.DEPLOY_RECEIPT_VIEW_SPEC = exports2.isVerifiedDeployReceiptView = exports2.asVerifiedDeployReceiptView = exports2.deployGate = exports2.gateDecision = exports2.RegistryConstructionError = exports2.guardToolRegistry = exports2.globToRegExp = exports2.matchGlob = exports2.blobMapKey = exports2.classifyByName = exports2.resolveArtifacts = exports2.REMEDIATION_LOOP_ATTESTATION_SPEC = exports2.isCasAttestation = exports2.readPriorBlockRemediation = exports2.readRemediationTransaction = exports2.buildRemediationLoopAttestation = exports2.COMMIT_EVIDENCE_MISSING = exports2.CAS_ATTESTATION_SPEC = exports2.strictCommitObservation = exports2.extractExecutorAttestationToken = exports2.evaluateCasEvidence = exports2.isExecuteIfUnchangedOutcome = exports2.isGuardExecutionProof = exports2.buildCasAttestation = exports2.REGISTRY_ABSENT_TOKEN = exports2.REGISTRY_VERSION_TOKEN_PREFIX = exports2.registryTokenRaw = exports2.writeRegistryIfUnchanged = exports2.createRegistryVersionToken = exports2.DB_ABSENT_TOKEN = exports2.DB_VERSION_TOKEN_PREFIX = exports2.dbTokenRaw = exports2.writeDbIfUnchanged = void 0;
|
|
75442
|
+
exports2.TOOLSET_ATTEST_ENVELOPE_TAG = exports2.TOOLSET_ATTEST_SIGNING_PREFIX = exports2.TOOLSET_ATTEST_VERSION = exports2.surfaceEnvelopeFields = exports2.isGuardOutcome = exports2.executeLangGraphToolCall = exports2.executeGeminiToolCall = exports2.executeAnthropicToolCall = exports2.executeOpenAIToolCall = exports2.executeProtectedTool = exports2.defaultSerializeGeminiToolResult = exports2.bindGeminiGuardOutcome = exports2.protectedToolToFunctionDeclaration = exports2.toGeminiTools = exports2.geminiToolAdapter = exports2.withCodeRiftsGemini = exports2.measureFsAuthorizationToken = exports2.measureFsAuthorization = exports2.wrapWriteWithFsCas = exports2.inferFullFileWriteContent = exports2.inferFsPathFromArgs = exports2.FRESHNESS_RESOLVER_FIX = exports2.freshnessRefusalTeaching = exports2.formatGateRefusalBody = exports2.defaultSerializeLangGraphToolResult = exports2.bindLangGraphGuardOutcome = exports2.LangGraphToolsNotStructuredError = exports2.isLangGraphReactAgentTool = exports2.bindLangGraphTools = exports2.protectedToolToLangGraph = exports2.toLangGraphTools = exports2.langGraphToolAdapter = exports2.withCodeRiftsLangGraph = exports2.defaultSerializeAnthropicToolResult = exports2.bindAnthropicGuardOutcome = exports2.protectedToolToAnthropic = exports2.toAnthropicTools = exports2.anthropicToolAdapter = exports2.withCodeRiftsAnthropic = exports2.defaultSerializeOpenAIToolResult = exports2.bindOpenAIGuardOutcome = exports2.protectedToolToOpenAI = exports2.toOpenAITools = exports2.openAIToolAdapter = exports2.withCodeRiftsOpenAI = exports2.formatCoverageObservedLine = exports2.freezeCoverageObserved = exports2.createCoverageObserver = exports2.readExecutionGrantToken = exports2.isExecutionGrantEnabled = void 0;
|
|
75443
|
+
exports2.kidFromToolsetAttestation = exports2.tryIssueToolsetAttestation = exports2.toolsetAttestSigningInput = exports2.declarationEntriesFromTools = exports2.computeToolsetDigest = exports2.TOOLSET_ATTEST_STATEMENT = void 0;
|
|
75304
75444
|
var guard_js_1 = require_guard();
|
|
75305
75445
|
Object.defineProperty(exports2, "guardToolCall", { enumerable: true, get: function() {
|
|
75306
75446
|
return guard_js_1.guardToolCall;
|
|
@@ -75589,6 +75729,18 @@ var require_cjs4 = __commonJS({
|
|
|
75589
75729
|
Object.defineProperty(exports2, "FS_ABSENT_TOKEN", { enumerable: true, get: function() {
|
|
75590
75730
|
return fs_js_1.FS_ABSENT_TOKEN;
|
|
75591
75731
|
} });
|
|
75732
|
+
Object.defineProperty(exports2, "assertSafeCasPath", { enumerable: true, get: function() {
|
|
75733
|
+
return fs_js_1.assertSafeCasPath;
|
|
75734
|
+
} });
|
|
75735
|
+
Object.defineProperty(exports2, "UnsafeCasPathError", { enumerable: true, get: function() {
|
|
75736
|
+
return fs_js_1.UnsafeCasPathError;
|
|
75737
|
+
} });
|
|
75738
|
+
Object.defineProperty(exports2, "fsObjectIdentity", { enumerable: true, get: function() {
|
|
75739
|
+
return fs_js_1.fsObjectIdentity;
|
|
75740
|
+
} });
|
|
75741
|
+
Object.defineProperty(exports2, "identitiesEqual", { enumerable: true, get: function() {
|
|
75742
|
+
return fs_js_1.identitiesEqual;
|
|
75743
|
+
} });
|
|
75592
75744
|
var api_js_1 = require_api3();
|
|
75593
75745
|
Object.defineProperty(exports2, "createApiVersionToken", { enumerable: true, get: function() {
|
|
75594
75746
|
return api_js_1.createApiVersionToken;
|
|
@@ -75876,6 +76028,12 @@ var require_cjs4 = __commonJS({
|
|
|
75876
76028
|
Object.defineProperty(exports2, "wrapWriteWithFsCas", { enumerable: true, get: function() {
|
|
75877
76029
|
return fs_default_wire_js_1.wrapWriteWithFsCas;
|
|
75878
76030
|
} });
|
|
76031
|
+
Object.defineProperty(exports2, "measureFsAuthorization", { enumerable: true, get: function() {
|
|
76032
|
+
return fs_default_wire_js_1.measureFsAuthorization;
|
|
76033
|
+
} });
|
|
76034
|
+
Object.defineProperty(exports2, "measureFsAuthorizationToken", { enumerable: true, get: function() {
|
|
76035
|
+
return fs_default_wire_js_1.measureFsAuthorizationToken;
|
|
76036
|
+
} });
|
|
75879
76037
|
var gemini_js_1 = require_gemini();
|
|
75880
76038
|
Object.defineProperty(exports2, "withCodeRiftsGemini", { enumerable: true, get: function() {
|
|
75881
76039
|
return gemini_js_1.withCodeRiftsGemini;
|
|
@@ -253975,6 +254133,16 @@ var require_github_enforcement = __commonJS({
|
|
|
253975
254133
|
RULESET_ABSENT: "ruleset_absent",
|
|
253976
254134
|
/** Rulesets could not be read at all. */
|
|
253977
254135
|
RULESET_UNREADABLE: "ruleset_unreadable",
|
|
254136
|
+
/** A ruleset requires our context but does not TARGET the branch that matters (1104a). */
|
|
254137
|
+
RULESET_REF_NOT_TARGETED: "ruleset_ref_not_targeted",
|
|
254138
|
+
/** Active, bound, targeted — but strict latest-head is off (1104b). Recorded, not fatal. */
|
|
254139
|
+
RULESET_STRICT_POLICY_OFF: "ruleset_strict_policy_off",
|
|
254140
|
+
/** 1102 — no active targeting ruleset requires a workflow at all. */
|
|
254141
|
+
REQUIRED_WORKFLOW_ABSENT: "required_workflow_absent",
|
|
254142
|
+
/** 1102 — a workflows rule exists but carries no sha, so it names a path and not bytes. */
|
|
254143
|
+
REQUIRED_WORKFLOW_UNPINNED: "required_workflow_unpinned",
|
|
254144
|
+
/** 1102 — a required workflow is pinned to a full commit sha. */
|
|
254145
|
+
REQUIRED_WORKFLOW_SHA_PINNED: "required_workflow_sha_pinned",
|
|
253978
254146
|
REQUIRED_CHECK_ABSENT: "required_check_absent",
|
|
253979
254147
|
ACTIVE_JOB: "active_job",
|
|
253980
254148
|
NO_ACTIVE_JOB: "no_active_job",
|
|
@@ -254005,6 +254173,41 @@ var require_github_enforcement = __commonJS({
|
|
|
254005
254173
|
const n = Number(value);
|
|
254006
254174
|
return Number.isInteger(n) && n > 0 ? n : null;
|
|
254007
254175
|
}
|
|
254176
|
+
function refPatterns(rs, key) {
|
|
254177
|
+
const rn = rs && rs.conditions && rs.conditions.ref_name;
|
|
254178
|
+
if (!rn || !Array.isArray(rn[key])) return null;
|
|
254179
|
+
return rn[key].map((x) => String(x));
|
|
254180
|
+
}
|
|
254181
|
+
function targetsDefaultBranch(entry, defaultBranch) {
|
|
254182
|
+
if (entry.target === "tag") return false;
|
|
254183
|
+
const inc = entry.ref_include;
|
|
254184
|
+
if (!Array.isArray(inc)) return null;
|
|
254185
|
+
const ref = defaultBranch ? `refs/heads/${defaultBranch}` : null;
|
|
254186
|
+
const matches = (pat) => {
|
|
254187
|
+
if (pat === "~ALL" || pat === "~DEFAULT_BRANCH") return true;
|
|
254188
|
+
if (!ref) return null;
|
|
254189
|
+
if (pat === ref) return true;
|
|
254190
|
+
if (!pat.includes("*") && !pat.includes("[") && !pat.includes("?")) return false;
|
|
254191
|
+
const rx = new RegExp(`^${pat.replace(/[.+^${}()|\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, ".")}$`);
|
|
254192
|
+
return rx.test(ref);
|
|
254193
|
+
};
|
|
254194
|
+
let included = false;
|
|
254195
|
+
for (const pat of inc) {
|
|
254196
|
+
const m = matches(pat);
|
|
254197
|
+
if (m === null) return null;
|
|
254198
|
+
if (m) {
|
|
254199
|
+
included = true;
|
|
254200
|
+
break;
|
|
254201
|
+
}
|
|
254202
|
+
}
|
|
254203
|
+
if (!included) return false;
|
|
254204
|
+
for (const pat of entry.ref_exclude || []) {
|
|
254205
|
+
const m = matches(pat);
|
|
254206
|
+
if (m === null) return null;
|
|
254207
|
+
if (m) return false;
|
|
254208
|
+
}
|
|
254209
|
+
return true;
|
|
254210
|
+
}
|
|
254008
254211
|
function rulesetRequiredCheckEntries(rulesets) {
|
|
254009
254212
|
const out = [];
|
|
254010
254213
|
const list = Array.isArray(rulesets) ? rulesets : [];
|
|
@@ -254023,7 +254226,15 @@ var require_github_enforcement = __commonJS({
|
|
|
254023
254226
|
enforcement: typeof rs.enforcement === "string" ? rs.enforcement : null,
|
|
254024
254227
|
integration_id: numericAppId(c && c.integration_id),
|
|
254025
254228
|
bypass_visible: Object.prototype.hasOwnProperty.call(rs, "bypass_actors"),
|
|
254026
|
-
bypass_count: Array.isArray(rs.bypass_actors) ? rs.bypass_actors.length : null
|
|
254229
|
+
bypass_count: Array.isArray(rs.bypass_actors) ? rs.bypass_actors.length : null,
|
|
254230
|
+
// 1104a — a ruleset can be ACTIVE and target nothing that matters. MEASURED against the
|
|
254231
|
+
// live API: `target` and `conditions.ref_name` are both in the detail document we already
|
|
254232
|
+
// fetch, so this is a grading change and needs no new call.
|
|
254233
|
+
target: typeof rs.target === "string" ? rs.target : null,
|
|
254234
|
+
ref_include: refPatterns(rs, "include"),
|
|
254235
|
+
ref_exclude: refPatterns(rs, "exclude"),
|
|
254236
|
+
// 1104b — same document, one boolean.
|
|
254237
|
+
strict_policy: typeof params.strict_required_status_checks_policy === "boolean" ? params.strict_required_status_checks_policy : null
|
|
254027
254238
|
});
|
|
254028
254239
|
}
|
|
254029
254240
|
}
|
|
@@ -254031,7 +254242,7 @@ var require_github_enforcement = __commonJS({
|
|
|
254031
254242
|
return out;
|
|
254032
254243
|
}
|
|
254033
254244
|
var RULESET_BYPASS_CAVEAT = " Bypass actors were not readable on this surface, so this does not establish that nobody can bypass the ruleset.";
|
|
254034
|
-
function evaluateRuleset(rulesets) {
|
|
254245
|
+
function evaluateRuleset(rulesets, defaultBranch = null) {
|
|
254035
254246
|
if (rulesets == null) {
|
|
254036
254247
|
return {
|
|
254037
254248
|
status: STATUS.UNVERIFIABLE,
|
|
@@ -254062,12 +254273,41 @@ var require_github_enforcement = __commonJS({
|
|
|
254062
254273
|
}
|
|
254063
254274
|
const bound = active.find((e) => e.integration_id === ENFORCING_ISSUER_APP_ID);
|
|
254064
254275
|
if (bound) {
|
|
254276
|
+
const targeted = targetsDefaultBranch(bound, defaultBranch);
|
|
254277
|
+
if (targeted === false) {
|
|
254278
|
+
return {
|
|
254279
|
+
status: STATUS.NOT_VERIFIED,
|
|
254280
|
+
reason_code: REASON.RULESET_REF_NOT_TARGETED,
|
|
254281
|
+
reason: `active ruleset "${bound.ruleset}" requires "${CHECK_NAME}" bound to the enforcing issuer, but its conditions do not target ${defaultBranch ? `refs/heads/${defaultBranch}` : "the default branch"}${bound.target === "tag" ? " (the ruleset targets tags, which never gate a merge to a branch)" : ""} \u2014 include: ${JSON.stringify(bound.ref_include)}, exclude: ${JSON.stringify(bound.ref_exclude || [])}`,
|
|
254282
|
+
ruleset_names: names,
|
|
254283
|
+
app_id: ENFORCING_ISSUER_APP_ID,
|
|
254284
|
+
ref_targeted: false
|
|
254285
|
+
};
|
|
254286
|
+
}
|
|
254287
|
+
if (targeted === null) {
|
|
254288
|
+
return {
|
|
254289
|
+
status: STATUS.UNVERIFIABLE,
|
|
254290
|
+
reason_code: REASON.RULESET_REF_NOT_TARGETED,
|
|
254291
|
+
reason: `active ruleset "${bound.ruleset}" binds "${CHECK_NAME}" to the enforcing issuer, but its ref conditions could not be read, so whether it targets the branch that matters CANNOT BE DETERMINED FROM HERE.${RULESET_BYPASS_CAVEAT}`,
|
|
254292
|
+
ruleset_names: names,
|
|
254293
|
+
app_id: ENFORCING_ISSUER_APP_ID,
|
|
254294
|
+
ref_targeted: null
|
|
254295
|
+
};
|
|
254296
|
+
}
|
|
254065
254297
|
return {
|
|
254066
254298
|
status: STATUS.VERIFIED,
|
|
254067
254299
|
reason_code: REASON.RULESET_ISSUER_BOUND,
|
|
254068
|
-
reason: `active ruleset "${bound.ruleset}" requires "${CHECK_NAME}" bound to integration_id ${ENFORCING_ISSUER_APP_ID} (${GITHUB_ACTIONS_APP_SLUG}) \u2014 the contract-gate Action, which concludes failure and blocks the merge
|
|
254300
|
+
reason: `active ruleset "${bound.ruleset}" requires "${CHECK_NAME}" bound to integration_id ${ENFORCING_ISSUER_APP_ID} (${GITHUB_ACTIONS_APP_SLUG}) \u2014 the contract-gate Action, which concludes failure and blocks the merge. It targets ${defaultBranch ? `refs/heads/${defaultBranch}` : "the default branch"}.` + (bound.strict_policy === true ? " strict_required_status_checks_policy is ON, so a stale head cannot merge." : bound.strict_policy === false ? " RESIDUAL: strict_required_status_checks_policy is OFF, so a branch whose head is behind the base can merge on a check that passed against older bytes." : " strict_required_status_checks_policy was not readable here.") + RULESET_BYPASS_CAVEAT,
|
|
254069
254301
|
ruleset_names: names,
|
|
254070
|
-
app_id: ENFORCING_ISSUER_APP_ID
|
|
254302
|
+
app_id: ENFORCING_ISSUER_APP_ID,
|
|
254303
|
+
ref_targeted: true,
|
|
254304
|
+
strict_policy: bound.strict_policy,
|
|
254305
|
+
strict_policy_residual: bound.strict_policy === false ? REASON.RULESET_STRICT_POLICY_OFF : null,
|
|
254306
|
+
// 1104c — NEVER an absence we cannot see. bypass_actors is in the ruleset schema but was
|
|
254307
|
+
// absent from all six live rulesets read without a credential (measured 2026-08-27), so
|
|
254308
|
+
// "not present in the response" is reported as NOT VISIBLE and never as "none configured".
|
|
254309
|
+
bypass_visible: bound.bypass_visible === true,
|
|
254310
|
+
bypass_count: bound.bypass_visible === true ? bound.bypass_count : null
|
|
254071
254311
|
};
|
|
254072
254312
|
}
|
|
254073
254313
|
const other = active.find((e) => e.integration_id != null);
|
|
@@ -254088,9 +254328,66 @@ var require_github_enforcement = __commonJS({
|
|
|
254088
254328
|
ruleset_names: names
|
|
254089
254329
|
};
|
|
254090
254330
|
}
|
|
254091
|
-
function
|
|
254331
|
+
function evaluateRequiredWorkflowPin(rulesets, defaultBranch = null) {
|
|
254332
|
+
if (rulesets == null) {
|
|
254333
|
+
return { status: STATUS.UNVERIFIABLE, reason_code: REASON.RULESET_UNREADABLE, pinned: null, workflows: [] };
|
|
254334
|
+
}
|
|
254335
|
+
const found = [];
|
|
254336
|
+
for (const rs of Array.isArray(rulesets) ? rulesets : []) {
|
|
254337
|
+
if (!rs || typeof rs !== "object") continue;
|
|
254338
|
+
if (rs.enforcement !== "active") continue;
|
|
254339
|
+
const entryShape = {
|
|
254340
|
+
target: typeof rs.target === "string" ? rs.target : null,
|
|
254341
|
+
ref_include: refPatterns(rs, "include"),
|
|
254342
|
+
ref_exclude: refPatterns(rs, "exclude")
|
|
254343
|
+
};
|
|
254344
|
+
if (targetsDefaultBranch(entryShape, defaultBranch) !== true) continue;
|
|
254345
|
+
for (const rule of Array.isArray(rs.rules) ? rs.rules : []) {
|
|
254346
|
+
if (!rule || rule.type !== "workflows") continue;
|
|
254347
|
+
const list = rule.parameters && Array.isArray(rule.parameters.workflows) ? rule.parameters.workflows : [];
|
|
254348
|
+
for (const w of list) {
|
|
254349
|
+
if (!w || typeof w !== "object") continue;
|
|
254350
|
+
found.push({
|
|
254351
|
+
ruleset: String(rs.name || rs.id || "ruleset"),
|
|
254352
|
+
path: typeof w.path === "string" ? w.path : null,
|
|
254353
|
+
repository_id: Number.isInteger(w.repository_id) ? w.repository_id : null,
|
|
254354
|
+
ref: typeof w.ref === "string" ? w.ref : null,
|
|
254355
|
+
// The whole question. sha is optional in the schema, so its ABSENCE is the finding.
|
|
254356
|
+
sha: typeof w.sha === "string" && /^[0-9a-f]{40}$/i.test(w.sha) ? w.sha : null
|
|
254357
|
+
});
|
|
254358
|
+
}
|
|
254359
|
+
}
|
|
254360
|
+
}
|
|
254361
|
+
if (found.length === 0) {
|
|
254362
|
+
return {
|
|
254363
|
+
status: STATUS.NOT_VERIFIED,
|
|
254364
|
+
reason_code: REASON.REQUIRED_WORKFLOW_ABSENT,
|
|
254365
|
+
pinned: false,
|
|
254366
|
+
workflows: [],
|
|
254367
|
+
reason: `no active ruleset targeting this branch requires a workflow, so the check that gates the merge is bound only to the SHARED issuer ${ENFORCING_ISSUER_APP_ID} (${GITHUB_ACTIONS_APP_SLUG}) \u2014 anyone who can edit .github/workflows can post under it`
|
|
254368
|
+
};
|
|
254369
|
+
}
|
|
254370
|
+
const pinned = found.filter((w) => w.sha != null);
|
|
254371
|
+
if (pinned.length === 0) {
|
|
254372
|
+
return {
|
|
254373
|
+
status: STATUS.NOT_VERIFIED,
|
|
254374
|
+
reason_code: REASON.REQUIRED_WORKFLOW_UNPINNED,
|
|
254375
|
+
pinned: false,
|
|
254376
|
+
workflows: found,
|
|
254377
|
+
reason: `a required workflow is configured (${found.map((w) => w.path || "?").join(", ")}) but NONE carries a sha. \`sha\` is optional in the rule schema, and without it the ruleset names a path rather than bytes, so rewriting that file still changes what runs`
|
|
254378
|
+
};
|
|
254379
|
+
}
|
|
254380
|
+
return {
|
|
254381
|
+
status: STATUS.VERIFIED,
|
|
254382
|
+
reason_code: REASON.REQUIRED_WORKFLOW_SHA_PINNED,
|
|
254383
|
+
pinned: true,
|
|
254384
|
+
workflows: found,
|
|
254385
|
+
reason: `active ruleset "${pinned[0].ruleset}" requires workflow ${pinned[0].path} pinned to sha ${pinned[0].sha} in repository_id ${pinned[0].repository_id}. Moving that pin requires editing the ruleset (repository administration), which is a different permission from workflow write. This does NOT establish that any pull request ran it, nor that bypass actors cannot skip the ruleset.`
|
|
254386
|
+
};
|
|
254387
|
+
}
|
|
254388
|
+
function evaluateRequiredCheckAcrossSurfaces(protection, rulesets, defaultBranch = null) {
|
|
254092
254389
|
const classic = evaluateRequiredCheck(protection);
|
|
254093
|
-
const ruleset = evaluateRuleset(rulesets);
|
|
254390
|
+
const ruleset = evaluateRuleset(rulesets, defaultBranch);
|
|
254094
254391
|
const merged = (base) => ({
|
|
254095
254392
|
...base,
|
|
254096
254393
|
surfaces_inspected: ["classic_branch_protection", rulesets == null ? "rulesets:unread" : "rulesets"],
|
|
@@ -254210,8 +254507,22 @@ var require_github_enforcement = __commonJS({
|
|
|
254210
254507
|
var STATUS = Object.freeze({
|
|
254211
254508
|
VERIFIED: "VERIFIED",
|
|
254212
254509
|
NOT_VERIFIED: "NOT_VERIFIED",
|
|
254213
|
-
UNVERIFIABLE: "UNVERIFIABLE"
|
|
254214
|
-
|
|
254510
|
+
UNVERIFIABLE: "UNVERIFIABLE",
|
|
254511
|
+
UNSUPPORTED_PLAN: "UNSUPPORTED_PLAN",
|
|
254512
|
+
PROVIDER_ERROR: "PROVIDER_ERROR"
|
|
254513
|
+
});
|
|
254514
|
+
var FRESHNESS = Object.freeze({ FRESH: "FRESH", STALE_EVIDENCE: "STALE_EVIDENCE" });
|
|
254515
|
+
function supportsStrongClaim(status) {
|
|
254516
|
+
return status === STATUS.VERIFIED;
|
|
254517
|
+
}
|
|
254518
|
+
var EVIDENCE_TTL_SECONDS = 3600;
|
|
254519
|
+
function evidenceFreshness(report, nowIso) {
|
|
254520
|
+
if (!report || !report.expires_at) return null;
|
|
254521
|
+
const now = Date.parse(nowIso || (/* @__PURE__ */ new Date()).toISOString());
|
|
254522
|
+
const exp = Date.parse(report.expires_at);
|
|
254523
|
+
if (!Number.isFinite(now) || !Number.isFinite(exp)) return null;
|
|
254524
|
+
return now > exp ? FRESHNESS.STALE_EVIDENCE : FRESHNESS.FRESH;
|
|
254525
|
+
}
|
|
254215
254526
|
function resolveGitHubAuth(env = process.env) {
|
|
254216
254527
|
const user = String(env && (env.GITHUB_TOKEN || env.GH_TOKEN) || "").trim();
|
|
254217
254528
|
if (user) {
|
|
@@ -254542,6 +254853,10 @@ var require_github_enforcement = __commonJS({
|
|
|
254542
254853
|
NO_TOKEN_INSTRUCTION,
|
|
254543
254854
|
LAYER_IDS,
|
|
254544
254855
|
STATUS,
|
|
254856
|
+
FRESHNESS,
|
|
254857
|
+
EVIDENCE_TTL_SECONDS,
|
|
254858
|
+
supportsStrongClaim,
|
|
254859
|
+
evidenceFreshness,
|
|
254545
254860
|
REASON,
|
|
254546
254861
|
CHECK_NAME,
|
|
254547
254862
|
GATE_ACTION,
|
|
@@ -254555,6 +254870,8 @@ var require_github_enforcement = __commonJS({
|
|
|
254555
254870
|
evaluateLayers,
|
|
254556
254871
|
evaluateRequiredCheck,
|
|
254557
254872
|
evaluateRuleset,
|
|
254873
|
+
evaluateRequiredWorkflowPin,
|
|
254874
|
+
targetsDefaultBranch,
|
|
254558
254875
|
evaluateRequiredCheckAcrossSurfaces,
|
|
254559
254876
|
verifiedRequiresBinding,
|
|
254560
254877
|
scanLocalWorkflows,
|
|
@@ -254941,7 +255258,7 @@ var require_setup_required_check = __commonJS({
|
|
|
254941
255258
|
}
|
|
254942
255259
|
} catch {
|
|
254943
255260
|
}
|
|
254944
|
-
const rulesetVerdict = evaluateRuleset(rulesetDocs);
|
|
255261
|
+
const rulesetVerdict = evaluateRuleset(rulesetDocs, branch);
|
|
254945
255262
|
const rulesetNamed = detectRulesetRequiredCheck(rulesetDocs || [], CHECK_NAME);
|
|
254946
255263
|
const ruleset = {
|
|
254947
255264
|
enforced: rulesetVerdict.status === STATUS.VERIFIED,
|
|
@@ -255314,6 +255631,8 @@ var require_enforce_check = __commonJS({
|
|
|
255314
255631
|
var chalk = require_source();
|
|
255315
255632
|
var {
|
|
255316
255633
|
EVIDENCE_SPEC,
|
|
255634
|
+
EVIDENCE_TTL_SECONDS,
|
|
255635
|
+
supportsStrongClaim,
|
|
255317
255636
|
APP_INSTALLATION,
|
|
255318
255637
|
NO_TOKEN_INSTRUCTION,
|
|
255319
255638
|
resolveGitHubAuth,
|
|
@@ -255325,6 +255644,35 @@ var require_enforce_check = __commonJS({
|
|
|
255325
255644
|
var { isValidRepo } = require_status();
|
|
255326
255645
|
var { resolveOwnerRepo } = require_setup_required_check();
|
|
255327
255646
|
if (process.env.NO_COLOR) chalk.level = 0;
|
|
255647
|
+
var crypto = require("node:crypto");
|
|
255648
|
+
var EVIDENCE_SCOPE = Object.freeze({
|
|
255649
|
+
grades: "repository configuration \u2014 what the provider would require of any pull request",
|
|
255650
|
+
covers: Object.freeze([
|
|
255651
|
+
"required status check present and named exactly",
|
|
255652
|
+
"the check bound to an issuer that can block (integration_id / app_id)",
|
|
255653
|
+
"ruleset enforcement is active rather than evaluate",
|
|
255654
|
+
"the ruleset targets the branch that matters",
|
|
255655
|
+
"strict latest-head policy recorded",
|
|
255656
|
+
"bypass actors reported as VISIBLE or NOT VISIBLE, never as absent"
|
|
255657
|
+
]),
|
|
255658
|
+
does_not_cover: Object.freeze([
|
|
255659
|
+
"which GitHub App actually posted a check run on any pull request",
|
|
255660
|
+
"the head_sha a check run executed against",
|
|
255661
|
+
"whether any check run concluded success",
|
|
255662
|
+
"a recomputable external proof hash over a specific run"
|
|
255663
|
+
]),
|
|
255664
|
+
why_not: "Those are facts about one pull request at one moment, not about configuration. Reading them needs a per-PR check-runs query this command does not make; a passing config is not a passing run."
|
|
255665
|
+
});
|
|
255666
|
+
function responseDigest(read) {
|
|
255667
|
+
if (!read) return null;
|
|
255668
|
+
return {
|
|
255669
|
+
endpoint: read.endpoint ? `GET ${read.endpoint}` : null,
|
|
255670
|
+
http_status: read.status == null ? null : read.status,
|
|
255671
|
+
// Hash of the BODY as we received it. Not the verdict — the INPUT to the verdict, so a
|
|
255672
|
+
// disagreement can be localised to the answer or to our grading of it.
|
|
255673
|
+
body_sha256: read.body === void 0 || read.body === null ? null : `sha256:${crypto.createHash("sha256").update(JSON.stringify(read.body)).digest("hex")}`
|
|
255674
|
+
};
|
|
255675
|
+
}
|
|
255328
255676
|
var USAGE = [
|
|
255329
255677
|
"Usage: coderifts enforce --check --repo owner/repo [--env <github-environment>] [--branch <name>] [--json]",
|
|
255330
255678
|
"",
|
|
@@ -255437,7 +255785,12 @@ var require_enforce_check = __commonJS({
|
|
|
255437
255785
|
const report2 = {
|
|
255438
255786
|
command: "enforce --check",
|
|
255439
255787
|
spec: EVIDENCE_SPEC,
|
|
255788
|
+
// OBSERVED_AT WAS NOT ADDED. `queried_at` already carries exactly that fact, and a
|
|
255789
|
+
// second name for one fact is the defect this round was told to look for.
|
|
255440
255790
|
queried_at: queriedAt,
|
|
255791
|
+
expires_at: new Date(Date.parse(queriedAt) + EVIDENCE_TTL_SECONDS * 1e3).toISOString(),
|
|
255792
|
+
evidence_ttl_seconds: EVIDENCE_TTL_SECONDS,
|
|
255793
|
+
evidence_scope: EVIDENCE_SCOPE,
|
|
255441
255794
|
repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
|
|
255442
255795
|
branch: options.branch || null,
|
|
255443
255796
|
environment: environmentName,
|
|
@@ -255481,14 +255834,33 @@ var require_enforce_check = __commonJS({
|
|
|
255481
255834
|
const report = {
|
|
255482
255835
|
command: "enforce --check",
|
|
255483
255836
|
spec: EVIDENCE_SPEC,
|
|
255837
|
+
// OBSERVED_AT WAS NOT ADDED. `queried_at` already carries exactly that fact, and a
|
|
255838
|
+
// second name for one fact is the defect this round was told to look for.
|
|
255484
255839
|
queried_at: queriedAt,
|
|
255840
|
+
expires_at: new Date(Date.parse(queriedAt) + EVIDENCE_TTL_SECONDS * 1e3).toISOString(),
|
|
255841
|
+
evidence_ttl_seconds: EVIDENCE_TTL_SECONDS,
|
|
255842
|
+
evidence_scope: EVIDENCE_SCOPE,
|
|
255485
255843
|
repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
|
|
255486
255844
|
branch,
|
|
255487
255845
|
environment: environmentName,
|
|
255488
255846
|
provider: "github",
|
|
255489
255847
|
auth: authPublic,
|
|
255848
|
+
// The raw answers behind the verdict, so a disagreement can be localised to the provider's
|
|
255849
|
+
// response or to our grading of it.
|
|
255850
|
+
responses: {
|
|
255851
|
+
protection: responseDigest(protectionRead),
|
|
255852
|
+
environment: responseDigest(environmentRead)
|
|
255853
|
+
},
|
|
255490
255854
|
...evaluated
|
|
255491
255855
|
};
|
|
255856
|
+
if (report.claim && report.claim.inescapable_deploy === true) {
|
|
255857
|
+
const unsupported = (report.layers || []).filter((l) => !supportsStrongClaim(l.status));
|
|
255858
|
+
if (unsupported.length > 0) {
|
|
255859
|
+
throw new Error(
|
|
255860
|
+
`enforce --check: inescapable_deploy is true with ${unsupported.length} layer(s) not VERIFIED (${unsupported.map((l) => `${l.id}=${l.status}`).join(", ")}) \u2014 refusing to emit`
|
|
255861
|
+
);
|
|
255862
|
+
}
|
|
255863
|
+
}
|
|
255492
255864
|
if (options.json) log(JSON.stringify(report, null, 2));
|
|
255493
255865
|
else log(renderHuman(report));
|
|
255494
255866
|
return { exitCode: 0, report };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coderifts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
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",
|
|
@@ -39,13 +39,13 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "node scripts/copy-corpus.js && node scripts/build.js",
|
|
42
|
-
"prepublishOnly": "node scripts/assert-guard-major.js && bash ../../scripts/freeze-gate.sh && npm run build",
|
|
42
|
+
"prepublishOnly": "node scripts/assert-guard-major.js && node scripts/assert-changelog-version.js && bash ../../scripts/freeze-gate.sh && npm run build",
|
|
43
43
|
"assert-guard-major": "node scripts/assert-guard-major.js",
|
|
44
44
|
"postinstall": "node scripts/postinstall.js",
|
|
45
45
|
"test": "node --test test/*.test.js"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@coderifts/agent-guard": "^
|
|
48
|
+
"@coderifts/agent-guard": "^13.0.0",
|
|
49
49
|
"chalk": "^4.1.2",
|
|
50
50
|
"cli-table3": "^0.6.4",
|
|
51
51
|
"commander": "^12.0.0",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CLI release gate (prepublishOnly): refuse to publish a version the CHANGELOG does not name.
|
|
6
|
+
*
|
|
7
|
+
* WHY THIS EXISTS — measured, not hypothetical. On 2026-08-27 packages/cli/CHANGELOG.md listed
|
|
8
|
+
* 4.10.0 and then "Unreleased", while npm carried 4.11.0, 4.12.0, 5.0.0 and 5.0.1. Four published
|
|
9
|
+
* versions were undocumented and a fifth (5.0.0) was published while still labelled Unreleased.
|
|
10
|
+
* Two distinct failure modes produced that, and BOTH are caught by the one check below:
|
|
11
|
+
*
|
|
12
|
+
* 1. The bump commit never touched the changelog at all. Measured: of the last eight CLI version
|
|
13
|
+
* bumps, four omitted it (4.11.0, 4.12.0, 4.13.0, 5.0.1) — and those four are exactly the
|
|
14
|
+
* versions that went missing. The correlation is not partial.
|
|
15
|
+
* 2. The bump commit DID write the entry, but under "## Unreleased", and nobody retitled it at
|
|
16
|
+
* publish. Atomicity alone does not save you: @coderifts/agent-guard 12.0.0 was written
|
|
17
|
+
* atomically and still shipped as "Unreleased", and 11.0.1 before it had to retitle a stale
|
|
18
|
+
* heading by hand.
|
|
19
|
+
*
|
|
20
|
+
* So the invariant is NOT "the bump touched the changelog" — it is that the version being published
|
|
21
|
+
* appears as its own heading. A human step that must be remembered at publish time is the step that
|
|
22
|
+
* gets skipped; this makes it fail the publish instead.
|
|
23
|
+
*
|
|
24
|
+
* Dependency-free on purpose: a publish gate that needs an install to run is a gate that stops
|
|
25
|
+
* running (same reasoning as assert-guard-major.js).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
const PKG_PATH = path.join(__dirname, '..', 'package.json');
|
|
32
|
+
const CHANGELOG_PATH = path.join(__dirname, '..', 'CHANGELOG.md');
|
|
33
|
+
|
|
34
|
+
function fail(msg) {
|
|
35
|
+
console.error(`assert-changelog-version: FAIL — ${msg}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Headings this file recognises as "this version is documented".
|
|
41
|
+
* A heading may carry a trailing note (e.g. "## 4.13.0 — never published"), so match the version
|
|
42
|
+
* token at a word boundary rather than requiring the line to be exactly the version.
|
|
43
|
+
*/
|
|
44
|
+
function headingNamesVersion(line, version) {
|
|
45
|
+
if (!/^##\s+/.test(line)) return false;
|
|
46
|
+
const rest = line.replace(/^##\s+/, '').trim();
|
|
47
|
+
const esc = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
48
|
+
return new RegExp(`^v?${esc}(\\b|$)`).test(rest);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function main() {
|
|
52
|
+
if (!fs.existsSync(PKG_PATH)) fail(`package.json not found at ${PKG_PATH}`);
|
|
53
|
+
if (!fs.existsSync(CHANGELOG_PATH)) fail(`CHANGELOG.md not found at ${CHANGELOG_PATH}`);
|
|
54
|
+
|
|
55
|
+
let version;
|
|
56
|
+
try {
|
|
57
|
+
version = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8')).version;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
fail(`package.json is not readable JSON: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
if (typeof version !== 'string' || !version.trim()) fail('package.json has no version string');
|
|
62
|
+
|
|
63
|
+
const lines = fs.readFileSync(CHANGELOG_PATH, 'utf8').split('\n');
|
|
64
|
+
const headings = lines.filter((l) => /^##\s+/.test(l));
|
|
65
|
+
if (headings.length === 0) fail('CHANGELOG.md has no "## " headings at all');
|
|
66
|
+
|
|
67
|
+
const match = headings.find((l) => headingNamesVersion(l, version));
|
|
68
|
+
if (!match) {
|
|
69
|
+
const listed = headings.slice(0, 4).map((h) => h.replace(/^##\s+/, '').trim());
|
|
70
|
+
fail(
|
|
71
|
+
`package.json is ${version} but CHANGELOG.md has no "## ${version}" heading.\n`
|
|
72
|
+
+ ` Top headings are: ${listed.join(' · ')}\n`
|
|
73
|
+
+ ' Publishing now is how a version goes undocumented. Retitle the Unreleased section to\n'
|
|
74
|
+
+ ` ${version}, or add its section, then publish.`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// "Unreleased" must never be the section that documents the version being published.
|
|
79
|
+
if (/^##\s+unreleased\b/i.test(match)) {
|
|
80
|
+
fail(`the section naming ${version} is the Unreleased heading — retitle it before publishing`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(`assert-changelog-version: OK — ${version} is documented ("${match.trim()}")`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main();
|
|
87
|
+
|
|
88
|
+
module.exports = { headingNamesVersion, PKG_PATH, CHANGELOG_PATH };
|
|
@@ -45,13 +45,33 @@
|
|
|
45
45
|
* raise, and no product code. The floor rises anyway, because a semantics floor is about
|
|
46
46
|
* what the CLI may RUN against: a guard that silently skips the atomicity demand on an ordinary
|
|
47
47
|
* edit is exactly the class of difference this gate exists to refuse.)
|
|
48
|
+
* 6.0.0: floor raised to 13 (the CAS now conditions on the AUTHORIZATION-TIME token. Through 12.0.0
|
|
49
|
+
* wrapWriteWithFsCas measured its expected token INSIDE executeFactory — after preflight and after
|
|
50
|
+
* the T2 recheck — and conditioned the write on that self-fetched value, which asks whether the
|
|
51
|
+
* state changed since a read a microsecond earlier. Measured on 12.0.0: a writer landing in that
|
|
52
|
+
* window had its state adopted as the legitimate starting point and the CAS still returned
|
|
53
|
+
* committed with enforced:true, clobbering it. 13.0.0 threads the T1 token, and adds three FS
|
|
54
|
+
* defences that were measured ABSENT rather than assumed: `..` refused before resolution, a
|
|
55
|
+
* symlinked final component refused (stat/readFile measured the TARGET while rename replaced the
|
|
56
|
+
* LINK — check and write named different objects), the token read through one file descriptor,
|
|
57
|
+
* and dev+ino recorded in the evidence. COST MEASURED AS ZERO, and measured more carefully than
|
|
58
|
+
* the last two raises: the CLI imports only deployGate, asVerifiedDeployReceiptView,
|
|
59
|
+
* DEPLOY_RECEIPT_VIEW_SPEC and matchGlob, whose implementing modules (deploy-gate.js,
|
|
60
|
+
* resolver-glob.js, deploy-receipt-token.js, merge-gate.js) are BYTE-IDENTICAL 12.0.0 -> 13.0.0.
|
|
61
|
+
* Note the honest qualifier the earlier raises did not state: requiring the barrel index.js LOADS
|
|
62
|
+
* every changed module, so the changed code is in the process — it is simply not on the code path
|
|
63
|
+
* of any symbol the CLI calls, and the four symbols return identical values under both versions.
|
|
64
|
+
* Suite staged against 13.0.0 returned an identical 539/518/21 with a byte-identical 28-item
|
|
65
|
+
* failure set. The floor rises anyway, because a semantics floor is about what the CLI may RUN
|
|
66
|
+
* against: a CAS that reports committed after adopting another writer's state is exactly the
|
|
67
|
+
* class this gate exists to refuse.)
|
|
48
68
|
*/
|
|
49
69
|
|
|
50
70
|
const fs = require('fs');
|
|
51
71
|
const path = require('path');
|
|
52
72
|
|
|
53
73
|
/** Minimum acceptable major for @coderifts/agent-guard in THIS package (the CLI). */
|
|
54
|
-
const MIN_GUARD_MAJOR =
|
|
74
|
+
const MIN_GUARD_MAJOR = 13;
|
|
55
75
|
const DEP = '@coderifts/agent-guard';
|
|
56
76
|
|
|
57
77
|
/**
|