tamperward 2.29.10 → 2.29.11

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/README.md CHANGED
@@ -311,6 +311,8 @@ used for CI-wiring and token-permission posture. A custom verifier workflow ther
311
311
  does not get judged against an unrelated/missing generated file, and a second verifier
312
312
  workflow with a write-scoped job cannot hide behind a safe canonical workflow.
313
313
 
314
+ For this repository, `tamperward doctor --github --repo hexrift/tamperward --branch main` also verifies the active main ruleset's Code Owner requirement, stale-review dismissal, every direct CI check named by `ci.yml`, and the absence of bypass actors. A missing setting is an authority failure, not a warning; run it with a token that can read repository rulesets.
315
+
314
316
  Local early-layer gaps remain posture findings rather than silently changing the
315
317
  existing generated-CI exit contract; hard CI/GitHub validation failures still exit 2.
316
318
 
package/dist/cli/index.js CHANGED
@@ -17386,10 +17386,10 @@ function emitReport(opts, checks, authoritative) {
17386
17386
  }
17387
17387
  }
17388
17388
  }
17389
- function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward") {
17390
- let hasRequiredCheck = false;
17389
+ function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward", requiredChecks = [requiredCheck]) {
17391
17390
  let hasCodeOwnerReview = false;
17392
17391
  let dismissesStaleReviews = false;
17392
+ const presentChecks = /* @__PURE__ */ new Set();
17393
17393
  if (Array.isArray(snapshot2.rules)) {
17394
17394
  for (const raw of snapshot2.rules) {
17395
17395
  const rule = asMapping(raw);
@@ -17402,10 +17402,10 @@ function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward") {
17402
17402
  if (rule.type === "required_status_checks") {
17403
17403
  const checks = params.required_status_checks;
17404
17404
  if (Array.isArray(checks)) {
17405
- hasRequiredCheck ||= checks.some((rawCheck) => {
17405
+ for (const rawCheck of checks) {
17406
17406
  const check2 = asMapping(rawCheck);
17407
- return check2?.context === requiredCheck;
17408
- });
17407
+ if (typeof check2?.context === "string") presentChecks.add(check2.context);
17408
+ }
17409
17409
  }
17410
17410
  }
17411
17411
  }
@@ -17418,20 +17418,26 @@ function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward") {
17418
17418
  if (status) {
17419
17419
  const contexts = status.contexts;
17420
17420
  if (Array.isArray(contexts)) {
17421
- hasRequiredCheck ||= contexts.some((x) => x === requiredCheck);
17421
+ for (const context of contexts) if (typeof context === "string") presentChecks.add(context);
17422
17422
  }
17423
17423
  const checks = status.checks;
17424
17424
  if (Array.isArray(checks)) {
17425
- hasRequiredCheck ||= checks.some((rawCheck) => {
17425
+ for (const rawCheck of checks) {
17426
17426
  const check2 = asMapping(rawCheck);
17427
- return check2?.context === requiredCheck;
17428
- });
17427
+ if (typeof check2?.context === "string") presentChecks.add(check2.context);
17428
+ }
17429
17429
  }
17430
17430
  }
17431
17431
  const findings = [];
17432
- if (!hasRequiredCheck) findings.push("require the tamperward status check");
17432
+ const missing = requiredChecks.filter((check2) => !presentChecks.has(check2));
17433
+ if (missing.length) findings.push(
17434
+ missing.length === 1 ? "require the " + missing[0] + " status check" : "require status checks: " + missing.join(", ")
17435
+ );
17433
17436
  if (!hasCodeOwnerReview) findings.push("require Code Owner review");
17434
17437
  if (!dismissesStaleReviews) findings.push("dismiss stale pull request approvals on new pushes");
17438
+ if (Array.isArray(snapshot2.bypassActors) && snapshot2.bypassActors.length > 0) {
17439
+ findings.push("remove unintended ruleset bypass actors");
17440
+ }
17435
17441
  return findings;
17436
17442
  }
17437
17443
  function gitText(cwd, args) {
@@ -17510,7 +17516,32 @@ function githubAuthority(opts, cwd) {
17510
17516
  } catch (e) {
17511
17517
  rulesError = e instanceof Error ? e : new Error(String(e));
17512
17518
  }
17513
- let findings = evaluateGitHubProtection({ rules });
17519
+ let bypassActors;
17520
+ let rulesetInspectionError = null;
17521
+ try {
17522
+ const rulesets = githubApi(cwd, "repos/" + repo + "/rulesets");
17523
+ const active = Array.isArray(rulesets) ? rulesets.find((raw) => {
17524
+ const item = asMapping(raw);
17525
+ return item?.name === "main" && item?.target === "branch" && item?.enforcement === "active";
17526
+ }) : null;
17527
+ const id = asMapping(active)?.id;
17528
+ if (typeof id !== "number") {
17529
+ rulesetInspectionError = new Error("no active main branch ruleset was found");
17530
+ } else {
17531
+ const detail = asMapping(githubApi(cwd, "repos/" + repo + "/rulesets/" + id));
17532
+ bypassActors = detail?.bypass_actors;
17533
+ }
17534
+ } catch (e) {
17535
+ rulesetInspectionError = e instanceof Error ? e : new Error(String(e));
17536
+ }
17537
+ let findings = evaluateGitHubProtection(
17538
+ { rules, bypassActors },
17539
+ "tamperward",
17540
+ REPOSITORY_REQUIRED_CHECKS
17541
+ );
17542
+ if (rulesetInspectionError) {
17543
+ findings.push("active main ruleset bypass actors could not be verified: " + rulesetInspectionError.message);
17544
+ }
17514
17545
  if (findings.length === 0) return { repo, branch, findings };
17515
17546
  let branchProtection;
17516
17547
  let classicError = null;
@@ -17725,7 +17756,7 @@ function runDoctor(opts = {}) {
17725
17756
  emitReport(opts, outcome.checks, outcome.authoritative);
17726
17757
  return outcome.code;
17727
17758
  }
17728
- var VERIFY_COMMAND, GITHUB_API_SCRIPT;
17759
+ var VERIFY_COMMAND, REPOSITORY_REQUIRED_CHECKS, GITHUB_API_SCRIPT;
17729
17760
  var init_doctor = __esm({
17730
17761
  "src/cli/doctor.ts"() {
17731
17762
  "use strict";
@@ -17741,6 +17772,25 @@ var init_doctor = __esm({
17741
17772
  init_narrow();
17742
17773
  init_repo_context();
17743
17774
  VERIFY_COMMAND = /\btamperward(?:@\S+)?\s+verify\b/;
17775
+ REPOSITORY_REQUIRED_CHECKS = [
17776
+ "typecheck",
17777
+ "test (20)",
17778
+ "test (22)",
17779
+ "test (24)",
17780
+ "build",
17781
+ "gate",
17782
+ // platform-contract is a matrix over os: [ubuntu-latest, macos-latest, windows-latest]
17783
+ // (see .github/workflows/ci.yml). GitHub publishes one check context per matrix cell,
17784
+ // named "<job> (<os>)"; the bare job name is never a real context, so it must be
17785
+ // enumerated per cell or the required-status check reports a false authority failure.
17786
+ "platform-contract (ubuntu-latest)",
17787
+ "platform-contract (macos-latest)",
17788
+ "platform-contract (windows-latest)",
17789
+ "harness-core",
17790
+ "round4-harness",
17791
+ "pilot-provisioning",
17792
+ "verifier-container"
17793
+ ];
17744
17794
  GITHUB_API_SCRIPT = [
17745
17795
  "const endpoint = process.argv[1];",
17746
17796
  "const token = process.env.TAMPERWARD_GITHUB_TOKEN || '';",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.29.10",
3
+ "version": "2.29.11",
4
4
  "description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "hexrift",