← Back to ledger

Ledger entry

Sequence 4628 · bugstack · governance_decision

Cryptographic proof

Hash
a39360fcc8da211f3e380d85e0184038bb9cddc86556735b9f901d2f4a86830d
Previous hash
ef61121c7565e7acb2abc6af202f87a8f6a8f0cb81bf729f46193620e8cae0c4
Ed25519 signature
NV3nqfje1LaFV6vr2wzuKeDi1nYLwPsRwl7Yh9ku3IbsGr/k72gbyJmZeHCfEd1lF4u+Y6xfmb99L5w7WhcnDw==
Signing public key (SPKI, base64)
MCowBQYDK2VwAyEAxFhLvd6zMtQx3v6VarlyTyW+m8JUK8asT736IYWEsus=
Published at
2026-08-17T20:54:08.957Z
Verify independently (no BugStack software)

The signature is Ed25519 over the hash above (as UTF-8). Fetch the org's keys at /org/bugstack/key — or use the key shown on this page — and run:

node -e '
const { createPublicKey, verify } = require("crypto");
const key = createPublicKey({ key: Buffer.from("<signing public key>", "base64"), type: "spki", format: "der" });
console.log(verify(null, Buffer.from("<hash>", "utf8"), key, Buffer.from("<signature>", "base64")));
'

Scrubbed payload

{
  "kind": "governance_decision",
  "input": {
    "orgId": "2e77b814-3c5d-4b48-965f-8e5475b92ba7",
    "agentKind": "fixer",
    "callerContext": {
      "actionId": "fa48f9c7-196d-4285-8537-6be6f48a33ca",
      "proposedByAgentRunId": "6cec2fc8-d087-4455-9418-c223e839a0f6"
    },
    "proposedAction": {
      "type": "open_pull_request",
      "target": {
        "repo": {
          "branch": "main",
          "repoFullName": "bugstackai/bugstack",
          "repositoryId": "c15eb17a-bf5f-4ded-8476-c309a3c705e7"
        },
        "type": "open_pull_request",
        "baseBranch": "main",
        "headBranch": "bugstack/deploy-fix/b12f7893-1785861871329"
      },
      "payload": {
        "body": "## 🤖 Deploy failure fix\n\nTest mock's zero-arg downloadSnapshot breaks mock.calls[0][0] typing\n\n### What failed\n- **Service**: bugstack-worker-tester (build_ended, deploy `dep-d9p1cjvlk1mc73a4aivg`)\n- **Commit range**: last green `532dbc540c` → failed `11b2b9ff17`\n- **Breaking commit**: `9aa4563bd3`\n\n### Root cause\nThe `github()` test helper in apps/workers/src/__tests__/cadence-scan.test.ts wraps `downloadSnapshot` in `vi.fn(async () => ...)` with no declared parameter, even though the real `downloadSnapshot` port takes an `{owner, repo, ref?, maxFileBytes?}` argument. Because the mocked function's inferred parameter list is empty, TypeScript infers `mock.calls[number]` as the empty tuple type `[]`, so `gh.downloadSnapshot.mock.calls[0]![0]` fails to typecheck (TS2493: tuple `[]` has no element at index 0), which cascades into `call` being typed as possibly `undefined` at every subsequent `call.xxx` access (TS18048) — exactly the 5 errors in the build log (lines 137-141). This is a test-authoring bug in code added by this branch, not an infra/config issue.\n\n### What changed\nGave the `github()` helper's `downloadSnapshot` mock an explicit (unused) parameter matching the real port signature (`{owner, repo, ref?, maxFileBytes?}`) so vi.fn infers a non-empty parameter tuple. This makes `gh.downloadSnapshot.mock.calls[0]` a 1-tuple, so indexing `[0]` type-checks and every subsequent `call.ref` / `call.maxFileBytes` / `call.owner` / `call.repo` access resolves to a defined type, clearing both the TS2493 and TS18048 errors without touching any other test or the module under test.\n\n<details><summary>Build/deploy log excerpt</summary>\n\n```\n#11 10.48 apps/dashboard postinstall: ✔ Generated Prisma Client (v5.22.0) to ./../../node_modules/.pnpm/@[email-redacted]@5.22.0/node_modules/@prisma/client in 126ms\n#11 10.13 apps/dashboard postinstall: Prisma schema loaded from ../../services/error-service/prisma/schema.prisma\n#11 8.196 apps/dashboard postinstall$ prisma generate\n#11 7.995 \n#11 7.995 + vitest 2.1.9\n#11 7.995 + typescript 5.9.3\n#11 7.995 + turbo 2.9.14\n#11 7.995 + @bugstack/cadence 0.0.0 <- packages/cadence\n#11 7.995 devDependencies:\n#11 7.995 \n#11 7.995 + pg 8.21.0\n#11 7.995 dependencies:\n#11 7.995 \n#11 7.387 Progress: resolved 840, reused 0, downloaded 837, added 840, done\n#11 7.241 Progress: resolved 840, reused 0, downloaded 836, added 839\n#11 6.241 Progress: resolved 840, reused 0, downloaded 832, added 834\n#11 5.535 \n#11 5.535    ╰───────────────────────────────────────────────╯\n#11 5.535    │                                               │\n#11 5.535    │   To update, run: corepack use [email-redacted].0   │\n#11 5.535    │     Changelog: https://pnpm.io/v/11.20.0      │\n#11 5.535    │     Update available! 10.29.3 → 11.20.0.      │\n#11 5.535    │                                               │\n#11 5.535    ╭───────────────────────────────────────────────╮\n#11 5.535 \n#11 5.238 Progress: resolved 840, reused 0, downloaded 824, added 287\n#11 4.238 Progress: resolved 840, reused 0, downloaded 556, added 173\n#11 3.217 Progress: resolved 840, reused 0, downloaded 305, added 79\n#11 2.213 Progress: resolved 840, reused 0, downloaded 0, added 0\n#11 1.618 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n```\n\n</details>\n\n### Confidence: high\n\n---\n_Generated by BugStack's deploy-failure playbook. Review before merging — auto-merge is gated on CI + governance._",
        "type": "open_pull_request",
        "files": [
          {
            "path": "apps/workers/src/__tests__/cadence-scan.test.ts",
            "content": "// apps/workers — the server-side cadence scan.\n//\n// The distinction these tests exist to protect: \"this repo is CLEAN\" and \"I\n// could not look at this repo\" must never collapse into the same outcome. The\n// whole org rides one member's OAuth token, so a repo that token cannot see is\n// invisible — and reporting invisible as clean is a false all-clear, the most\n// expensive kind of wrong answer a scanner can give.\n\nimport { describe, expect, it, vi } from \"vitest\";\n\nimport { scanOneRepo, type ScanTarget } from \"../cadence-scan\";\n\nconst TARGET: ScanTarget = {\n  orgId: \"org-1\",\n  repositoryId: \"repo-1\",\n  repoFullName: \"acme/svc\",\n  defaultBranch: \"main\",\n};\n\nconst OFFENDING = `import { getDb } from \"@bugstack/db\";\nconst REAP_MS = 60 * 1000;\nsetInterval(reap, REAP_MS);\n`;\n\nconst NEON_ENV = \"DATABASE_URL=postgres://[email-redacted]/app\";\n\n/**\n * The mocked `downloadSnapshot` must declare the same parameter the real port\n * takes (`{owner, repo, ref?, maxFileBytes?}`), even though this helper never\n * reads it. `vi.fn(async () => ...)` with a zero-arg function makes\n * TypeScript infer `mock.calls[number]` as the EMPTY tuple `[]`, so\n * `mock.calls[0][0]` fails to typecheck (TS2493) and every downstream\n * `call.xxx` access on that undefined-typed result cascades into TS18048.\n */\nfunction github(files: Record<string, string>) {\n  return {\n    downloadSnapshot: vi.fn(\n      async (_input: { owner: string; repo: string; ref?: string; maxFileBytes?: number }) =>\n        Object.entries(files).map(([path, text]) => ({ path, size: text.length, text })),\n    ),\n  };\n}\n\nfunction deps() {\n  const raised: Array<Record<string, unknown>> = [];\n  return {\n    raised,\n    raiseSignal: async (sig: Record<string, unknown>) => {\n      raised.push(sig);\n    },\n    today: () => \"2026-08-04\",\n  };\n}\n\ndescribe(\"scanOneRepo\", () => {\n  it(\"raises ONE signal for a repo with findings, in the playbook's shape\", async () => {\n    const d = deps();\n    const out = await scanOneRepo(\n      TARGET,\n      github({ \".env.example\": NEON_ENV, \"src/jobs.ts\": OFFENDING }),\n      d as never,\n    );\n\n    expect(out).toEqual({ kind: \"raised\", findings: 1 });\n    expect(d.raised).toHaveLength(1);\n\n    const sig = d.raised[0]!;\n    expect(sig.kind).toBe(\"anomaly\");\n    // Load-bearing: REPO_REQUIRING_KINDS gates fixer dispatch on a linked\n    // repository, so a null here ends the loop at \"tell a human\".\n    expect(sig.repositoryId).toBe(\"repo-1\");\n\n    const payload = sig.payload as Record<string, unknown>;\n    // The SAME discriminator the CI bridge emits — one playbook, two entry\n    // points.\n    expect(payload.playbook).toBe(\"cadence\");\n    expect(payload.source).toBe(\"server_side_cadence_scan\");\n    expect(payload.suggestedAgents).toEqual([\"fixer\"]);\n    expect(String(payload.summary)).toContain(\"acme/svc\");\n    expect(String(payload.summary)).toContain(\"neon\");\n  });\n\n  it(\"dedups to one signal per repo per DAY\", async () => {\n    // The scan is whole-tree with no base ref to diff against, so every pass\n    // re-reports every standing violation. Without a stable per-day key the\n    // same finding would open a fresh incident daily.\n    const files = { \".env.example\": NEON_ENV, \"src/jobs.ts\": OFFENDING };\n    const a = deps();\n    await scanOneRepo(TARGET, github(files), a as never);\n    const b = deps();\n    await scanOneRepo(TARGET, github(files), b as never);\n    expect(a.raised[0]!.externalId).toBe(b.raised[0]!.externalId);\n    expect(a.raised[0]!.externalId).toBe(\"cadence-scan:repo-1:2026-08-04\");\n  });\n\n  it(\"distinguishes CLEAN from COULD-NOT-LOOK\", async () => {\n    // Clean: the token saw the repo and there was nothing to report.\n    const clean = await scanOneRepo(\n      TARGET,\n      github({\n        \".env.example\": NEON_ENV,\n        \"src/ok.ts\": 'import { getDb } from \"@bugstack/db\";\\nalignedInterval(1800000, t);',\n      }),\n      deps() as never,\n    );\n    expect(clean).toEqual({ kind: \"skipped\", reason: \"clean\" });\n\n    // Not visible: the org's single OAuth token cannot see this repo. This is\n    // NOT an all-clear and must never be reported as one.\n    const blind = await scanOneRepo(\n      TARGET,\n      {\n        downloadSnapshot: async () => {\n          throw new Error(\"Not Found\");\n        },\n      },\n      deps() as never,\n    );\n    expect(blind).toMatchObject({ kind: \"skipped\", reason: \"not_visible\" });\n    expect((blind as { detail?: string }).detail).toContain(\"Not Found\");\n  });\n\n  it(\"stays silent on a repo with no idle-suspend database\", async () => {\n    const out = await scanOneRepo(\n      TARGET,\n      github({\n        \".env.example\": \"DATABASE_URL=postgres://[email-redacted]:5432/app\",\n        \"src/jobs.ts\": OFFENDING,\n      }),\n      deps() as never,\n    );\n    expect(out).toEqual({ kind: \"skipped\", reason: \"not_armed\" });\n  });\n\n  it(\"skips honestly when the org has no GitHub connector\", async () => {\n    const out = await scanOneRepo(TARGET, null, deps() as never);\n    expect(out).toEqual({ kind: \"skipped\", reason: \"no_connector\" });\n  });\n\n  it(\"asks for an explicit ref and a per-file cap\", async () => {\n    // downloadSnapshot defaults ref to \"\" — an empty path segment, not the\n    // default branch. And an uncapped snapshot of a large repo is how this\n    // worker OOMs on a 2GB box.\n    const gh = github({ \".env.example\": NEON_ENV, \"src/jobs.ts\": OFFENDING });\n    await scanOneRepo(TARGET, gh, deps() as never);\n    const call = gh.downloadSnapshot.mock.calls[0]![0];\n    expect(call.ref).toBe(\"main\");\n    expect(call.maxFileBytes).toBeGreaterThan(0);\n    expect(call.owner).toBe(\"acme\");\n    expect(call.repo).toBe(\"svc\");\n  });\n\n  it(\"tolerates null file text without throwing\", async () => {\n    // Anything past maxFileBytes comes back as text:null.\n    const gh = {\n      downloadSnapshot: async () => [\n        { path: \"src/big.ts\", size: 999_999, text: null },\n        { path: \".env.example\", size: 40, text: NEON_ENV },\n        { path: \"src/jobs.ts\", size: OFFENDING.length, text: OFFENDING },\n      ],\n    };\n    const out = await scanOneRepo(TARGET, gh, deps() as never);\n    expect(out).toEqual({ kind: \"raised\", findings: 1 });\n  });\n});\n"
          }
        ],
        "title": "🤖 Deploy fix: Test mock's zero-arg downloadSnapshot breaks mock.calls[0][0] typing"
      },
      "metadata": {
        "citations": [
          {
            "ref": "b12f7893-c6f7-4f86-b931-c51e18bd89f5",
            "kind": "signal",
            "detail": "render deploy failure (build_ended on bugstack-worker-tester)"
          }
        ],
        "hypotheses": {
          "items": [
            {
              "chosen": true,
              "statement": "The github() vi.fn mock's zero-arg signature makes TS infer mock.calls entries as the empty tuple `[]`, breaking calls[0][0] indexing and cascading to 'possibly undefined' on every field access",
              "confidence": 0.92
            },
            {
              "statement": "A tsconfig/strict-mode setting change elsewhere in the diff tightened checking and exposed a pre-existing latent bug in this test file",
              "confidence": 0.2
            },
            {
              "statement": "The vitest/TypeScript version bump changed vi.fn's generic inference behavior for zero-arg async functions",
              "confidence": 0.1
            }
          ],
          "margin": 0.72
        },
        "provenance": {
          "model": "claude-sonnet-5",
          "agentKind": "fixer"
        },
        "computedConfidence": {
          "basis": {
            "diff_size": {
              "note": "1 file(s), ~6165 chars",
              "available": true,
              "contribution": 0
            },
            "danger_zone": {
              "note": "not built yet",
              "available": false
            },
            "self_report": {
              "note": "high — carries no gating weight (§10.5)",
              "available": true,
              "contribution": 0
            },
            "blast_radius": {
              "available": false
            },
            "reproduction": {
              "available": false
            },
            "signal_richness": {
              "note": "not built yet",
              "available": false
            },
            "calibration_table": {
              "note": "recorded (P3.4); not consulted until P4.1",
              "available": false
            },
            "hypothesis_margin": {
              "note": "margin=0.72 over 3 hypotheses",
              "available": true,
              "contribution": 0.1
            },
            "reproduction_test": {
              "available": false
            }
          },
          "score": 0.45,
          "missing": [
            "reproduction",
            "reproduction_test",
            "blast_radius",
            "signal_richness",
            "danger_zone",
            "calibration_table"
          ],
          "version": 1,
          "flooredBySelfReport": false
        }
      },
      "riskClass": null,
      "confidence": "high",
      "targetKind": "code",
      "environment": "prod"
    }
  },
  "output": {
    "reason": "Per-repo autonomy level: autopilot.",
    "decision": "propose_and_auto_merge_on_test_pass",
    "evaluators": [
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "fileWarn": 20,
          "lineWarn": 500,
          "fileCount": 1,
          "fileLimit": 50,
          "lineCount": 166,
          "lineLimit": 2000
        },
        "evaluatorId": "diff_size"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "note": "no riskClass on input",
          "riskClass": null,
          "environment": "prod"
        },
        "evaluatorId": "risk_class"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "override": false,
          "actionType": "open_pull_request",
          "environment": "prod"
        },
        "evaluatorId": "environment_floor"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "actionType": "open_pull_request",
          "environment": "prod"
        },
        "evaluatorId": "action_kind"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "confidence": "high"
        },
        "evaluatorId": "confidence"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "intact": true
        },
        "evaluatorId": "signature_chain"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "note": "no tests metadata; type has a downstream gate"
        },
        "evaluatorId": "test_coverage"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "note": "reproduction contract gates fixer merges only",
          "applicable": false
        },
        "evaluatorId": "reproduction"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "note": "no blastRadius metadata",
          "stub": true
        },
        "evaluatorId": "blast_radius"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "note": "no licenses metadata",
          "stub": true
        },
        "evaluatorId": "license_compliance"
      },
      {
        "verdict": {
          "result": "pass"
        },
        "structured": {
          "note": "no branchProtection metadata",
          "stub": true
        },
        "evaluatorId": "branch_protection"
      }
    ],
    "matchedRuleId": null,
    "matchedRuleHash": null,
    "matchedRulePriority": null
  }
}

Redaction metadata

{
  "patternMatches": [
    "email",
    "email"
  ],
  "redactedFields": [],
  "scrubberVersion": "1.0.0"
}