Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions grant-compute-budget-guard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "scibase-grant-compute-budget-guard",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"test": "node --test test/*.test.js",
"demo": "node scripts/demo.js"
}
}
22 changes: 22 additions & 0 deletions grant-compute-budget-guard/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Grant Compute Budget Guard

This module contributes to SCIBASE issue #20, Revenue Infrastructure.

It evaluates AI compute usage before the platform converts usage into billable spend. The guard checks grant award validity, billing holds, usage budgets, institutional invoice requirements, and high-margin overage rules so research teams do not accidentally create unrecoverable compute charges.

## Local Verification

```bash
npm test
npm run demo
```

## Demo Evidence

The demo transcript is captured in `reports/demo-transcript.md`. The demo generates these reviewer artifacts:

- `reports/compute-budget-report.md`
- `reports/compute-budget-packet.json`
- `reports/summary.svg`

The demo data is synthetic and does not call payment processors, cloud providers, or grant systems.
100 changes: 100 additions & 0 deletions grant-compute-budget-guard/reports/compute-budget-packet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"title": "SCIBASE Grant Compute Budget Guard",
"issue": "SCIBASE.AI#20",
"claim": "/claim #20",
"evaluation": {
"status": "hold_billing",
"generatedAt": "2026-06-13T18:30:00.000Z",
"packetId": "scibase-grant-compute-budget-demo",
"digest": "a0876258828149a3de20dd81473f46891355c1b423bb7d3f71b81377dacf2d7a",
"counts": {
"accounts": 2,
"grants": 2,
"blockers": 6,
"warnings": 0,
"heldAccounts": 1
},
"blockers": [
{
"code": "missing_award_id",
"accountId": "acct-lab-hold",
"message": "Grant-backed account is missing an award id."
},
{
"code": "expired_grant",
"accountId": "acct-lab-hold",
"message": "Grant has expired before pending usage billing."
},
{
"code": "grant_billing_hold",
"accountId": "acct-lab-hold",
"message": "Grant is under a billing hold."
},
{
"code": "missing_purchase_order",
"accountId": "acct-lab-hold",
"message": "Institutional invoice account is missing a purchase order."
},
{
"code": "budget_overage",
"accountId": "acct-lab-hold",
"message": "Projected compute spend exceeds the configured budget limit.",
"projectedSpend": 5750,
"budgetLimit": 5000,
"overagePercent": 15
},
{
"code": "low_compute_margin",
"accountId": "acct-lab-hold",
"message": "Pending usage falls below the minimum gross margin threshold.",
"grossMarginPercent": 20,
"minGrossMarginPercent": 30
}
],
"warnings": [],
"decisions": [
{
"accountId": "acct-lab-stable",
"institution": "North Campus Lab",
"decision": "bill",
"reasons": [],
"requiredActions": []
},
{
"accountId": "acct-lab-hold",
"institution": "Materials Institute",
"decision": "hold",
"reasons": [
"missing_award_id",
"expired_grant",
"grant_billing_hold",
"missing_purchase_order",
"budget_overage",
"low_compute_margin"
],
"requiredActions": [
"Add the grant award id before recognizing sponsored compute revenue.",
"Move usage to a renewed grant or institutional invoice before billing.",
"Resolve the sponsor billing hold before creating an invoice.",
"Attach a purchase order for institutional invoicing.",
"Require budget-owner approval before converting usage to billable spend.",
"Reprice the compute job or route it to a cheaper execution tier."
]
}
],
"policy": {
"maxOveragePercent": 5,
"minGrossMarginPercent": 30,
"requirePurchaseOrderForInvoice": true,
"requireGrantAwardId": true,
"blockExpiredGrants": true
}
},
"reviewerChecklist": [
"Grant-backed usage has an award id and active grant window.",
"Institutional invoice accounts include purchase orders.",
"Pending AI compute charges stay within approved budget limits.",
"Billing holds stop invoices before revenue recognition.",
"Compute usage preserves the configured gross margin floor."
]
}
25 changes: 25 additions & 0 deletions grant-compute-budget-guard/reports/compute-budget-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Grant Compute Budget Guard Report

Issue: SCIBASE.AI#20
Claim marker: `/claim #20`
Status: `hold_billing`
Digest: `a0876258828149a3de20dd81473f46891355c1b423bb7d3f71b81377dacf2d7a`

## Reviewer Checklist
- Grant-backed usage has an award id and active grant window.
- Institutional invoice accounts include purchase orders.
- Pending AI compute charges stay within approved budget limits.
- Billing holds stop invoices before revenue recognition.
- Compute usage preserves the configured gross margin floor.

## Blockers
- missing_award_id: Grant-backed account is missing an award id.
- expired_grant: Grant has expired before pending usage billing.
- grant_billing_hold: Grant is under a billing hold.
- missing_purchase_order: Institutional invoice account is missing a purchase order.
- budget_overage: Projected compute spend exceeds the configured budget limit.
- low_compute_margin: Pending usage falls below the minimum gross margin threshold.

## Account Decisions
- acct-lab-stable: bill
- acct-lab-hold: hold (missing_award_id, expired_grant, grant_billing_hold, missing_purchase_order, budget_overage, low_compute_margin)
35 changes: 35 additions & 0 deletions grant-compute-budget-guard/reports/demo-transcript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Grant Compute Budget Guard Demo Transcript

Date verified: 2026-06-15

Commands run from `grant-compute-budget-guard`:

```bash
npm test
npm run demo
```

Test result:

```text
5 tests passed
0 tests failed
```

Demo output:

```json
{
"status": "hold_billing",
"digest": "a0876258828149a3de20dd81473f46891355c1b423bb7d3f71b81377dacf2d7a",
"blockers": 6,
"heldAccounts": 1,
"reportsDir": "grant-compute-budget-guard/reports"
}
```

Generated reviewer artifacts:

- `reports/compute-budget-report.md`
- `reports/compute-budget-packet.json`
- `reports/summary.svg`
1 change: 1 addition & 0 deletions grant-compute-budget-guard/reports/summary.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions grant-compute-budget-guard/scripts/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

import {
buildReviewerPacket,
demoPacket,
renderMarkdownReport,
renderSvgSummary
} from "../src/index.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const moduleRoot = path.resolve(__dirname, "..");
const reportsDir = path.join(moduleRoot, "reports");
const packet = demoPacket();
const reviewerPacket = buildReviewerPacket(packet, { now: packet.generatedAt });

fs.mkdirSync(reportsDir, { recursive: true });
fs.writeFileSync(path.join(reportsDir, "compute-budget-packet.json"), `${JSON.stringify(reviewerPacket, null, 2)}\n`);
fs.writeFileSync(path.join(reportsDir, "compute-budget-report.md"), renderMarkdownReport(packet, { now: packet.generatedAt }));
fs.writeFileSync(path.join(reportsDir, "summary.svg"), renderSvgSummary(packet, { now: packet.generatedAt }));

console.log(JSON.stringify({
status: reviewerPacket.evaluation.status,
digest: reviewerPacket.evaluation.digest,
blockers: reviewerPacket.evaluation.counts.blockers,
heldAccounts: reviewerPacket.evaluation.counts.heldAccounts,
reportsDir
}, null, 2));
Loading