# Project export: Arsenal-lint

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: ArsenalLint typechecks agent policies against AI-Arsenal tips offline: capability diffs, fail-closed gates, guarded replay, and signed receipts—built with Codex + GPT-5.6.
- Devpost: https://devpost.com/software/arsenal-lint
- GitHub: https://github.com/knarayanareddy/arsenal-lint
- Video: https://www.youtube.com/embed/xEgW7qcxyCM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — knarayanareddy (5 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# ArsenalLint

ArsenalLint is an offline, deterministic policy typechecker and proof loop for a deliberately supported TypeScript agent-manifest convention. It proves only that a supported manifest/code path passed the versioned checks and supplied replay tests; it is not a proof of general agent safety, production compliance, or complete TypeScript analysis.

## Architecture

`TypeScript defineAgent literal → capability extraction/diff → versioned deterministic rules → fail-closed guard → JSONL replay → Ed25519 receipt → self-contained HTML report`.

The supported convention is a literal `defineAgent({ roles, runtime, guardrails })` call. Roles have literal `id`, `tools`, and optional `irreversible_tools`; runtime and guardrail values must be literals. Spread assignments, computed names, shorthand/dynamic properties, and other unsupported syntax are reported as unknown and make source-policy gates and attestations fail closed. Runtime enforcement covers only calls routed through the guard. No LLM or network/API call participates in the verdict.

The catalog is read-only. Set `ARSENAL_DATA_PATH=/path/to/AI-Arsenal/data`; its actual loaded commit is authoritative and a differing `policy.catalog_commit` is rejected. `fixtures/catalog` is the minimal offline catalog used by tests and CI.

## Install and verify

Requires Node 22+ and pnpm 9+. Supported platforms are macOS, Linux, and Windows. The fully offline judge path needs no model credentials, network access, or AI-Arsenal checkout.

```bash
pnpm install --frozen-lockfile
pnpm build
pnpm test
pnpm demo:ci
```

The final command writes `output/arsenallint-report.html` and demonstrates the supplied unapproved `apply_change` being blocked, followed by retry-limit blocking.

For the real read-only catalog, use the checked-out contract pin:

```bash
export ARSENAL_DATA_PATH=/Users/macbookprom1pro/AI-Arsenal/data
pnpm arsenallint doctor
```

`doctor` reports the actual containing Git commit; ArsenalLint rejects any `policy.catalog_commit` that differs from it.

## CLI

```bash
pnpm arsenallint doctor
pnpm arsenallint extract examples/hardened-agent.ts
pnpm arsenallint diff examples/baseline-agent.ts examples/unsafe-agent.ts
pnpm arsenallint check fixtures/pass.yaml --report output/report.html
pnpm arsenallint gate --policy fixtures/pass.yaml --source examples/hardened-agent.ts
pnpm arsenallint guard fixtures/pass.yaml --source examples/hardened-agent.ts
pnpm arsenallint replay fixtures/pass.yaml fixtures/traces/adversarial.jsonl
pnpm arsenallint keygen --out output/keys
pnpm arsenallint attest fixtures/pass.yaml --source examples/hardened-agent.ts --trace fixtures/traces/adversarial.jsonl --private-key output/keys/private.pem --out output/receipt.json
pnpm arsenallint verify output/receipt.json --policy fixtures/pass.yaml --source examples/hardened-agent.ts --trace fixtures/traces/adversarial.jsonl --public-key output/keys/public.pem
pnpm arsenallint demo
```

The concise narrated walkthrough is in [docs/demo-script.md](docs/demo-script.md). Its sequence is hidden capability → cited FAIL → guarded block → PASS → signed receipt → tamper rejection.

Receipts use Ed25519 and bind policy and capability digests, cited tips, portable Git/fixture catalog provenance and content digest, source commit and source-content digest, rule result, and replay result. Attestation rejects source-policy contradictions; `gate` is the CI policy/source check. Tampering with any bound input makes verification fail. Signatures establish payload integrity and key possession—not safety, signer trust, or deployment correctness.

## License

ArsenalLint code is licensed under the MIT License. See [LICENSE](LICENSE).

Bundled Arsenal fixture/catalog content is derived from [AI-Arsenal](https://github.com/knarayanareddy/AI-Arsenal) at commit `ca724eb19ae55efc04611817452b5c8e00ec9f2d` and is licensed under CC-BY-4.0. See [LICENSE-CONTENT](LICENSE-CONTENT).

Attribution does not imply upstream endorsement.

## Limits and development evidence

`RULES.md`, `rules.map.json`, and the golden fixtures are the evaluator contract. The runtime guard protects only calls routed through it. Replay is deterministic for the hand-authored JSONL trace and does not reproduce model or external-service nondeterminism. The report’s trust badges expose catalog provenance; independent findings are a ranked remediation plan, never tip contradictions.

Codex and GPT-5.6 were used as development collaborators to translate the written contract into typed predicates, fixtures, tests, replay, report, and CI. GPT-5.6 is not a runtime dependency and does not participate in any verdict.

`CODEX_SESSION_ID: 019f85cb-b4e6-7d83-a1fd-b562f378061c`


## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 87 KB.
- TypeScript (language) — detected in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (26 of 26)

```
.github/workflows/arsenal-lint.yml
.gitignore
AGENTS.md
CODEX_HANDOFF.md
docs/demo-script.md
examples/baseline-agent.ts
examples/hardened-agent.ts
examples/unsafe-agent.ts
fixtures/catalog/tips.json
fixtures/conflict.yaml
fixtures/expected/conflict.json
fixtures/expected/fail.json
fixtures/expected/pass.json
fixtures/fail.yaml
fixtures/pass.yaml
fixtures/traces/adversarial.jsonl
LICENSE
LICENSE-CONTENT
package.json
README.md
rules.map.json
RULES.md
src/arsenallint-cli.ts
src/arsenallint.ts
tests/arsenallint.test.ts
tsconfig.json
```

### Dependencies

- package.json: @types/node@^22.15.0, typescript@^5.8.3

### Recent commits (newest first)

- Merge pull request #4 from knarayanareddy/docs/add-license
- Add code and catalog content licenses
- Merge pull request #3 from knarayanareddy/codex/arsenallint-final-hardening
- Address CodeRabbit hardening review comments
- Merge pull request #2 from knarayanareddy/codex/arsenallint-hardening
- Harden ArsenalLint proof-loop trust boundaries
- Merge pull request #1 from knarayanareddy/codex/arsenallint
- Restore TypeScript project configuration
- Fix pnpm setup in CI
- Build deterministic ArsenalLint proof loop
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### AGENTS.md

```markdown
# ArsenalLint

## Working rules

- Keep the core library independent of CLI and transport concerns.
- Treat AI-Arsenal generated JSON as read-only input.
- Never emit or trust an Arsenal citation unless its ID exists in the loaded catalog.
- Keep fixture mode working without network access or credentials.
- Generated scaffold output must remain under `output/` and must not overwrite without `--force`.
- Run `pnpm build && pnpm test` after changes.

## MVP boundary

The deterministic bounded-agent proof loop is the primary demo. No runtime model/API synthesis is part of ArsenalLint.

```

### RULES.md

```markdown
# ArsenalLint — Rule Contract (single source of truth)

> Policy typechecker for agent systems. Proves — with a cryptographic seal — which
> Arsenal guardrail tips a policy satisfies at catalog commit `ca724eb…`, and flags
> when your claims don't match your gates.

**Category:** Developer Tools. **Built with:** Codex + GPT-5.6 (core implemented in the
Codex session; this contract was produced by the planning agents).

This file and `rules.map.json` are the canonical contract. The engine, tests, and fixtures
must stay in sync with them.

---

## 1. Verdict object schema

```json
{
  "policy": "string (fixture name)",
  "status": "FAIL | WARN | PASS",
  "findings": [
    { "rule": "R1", "arsenal_tip_id": "require-human-approval-for-irreversible-actions",
      "severity": "high", "weight": 3.0,
      "evidence": "irreversible tool 'apply_change' exposed without human_approval_for gate",
      "verification_status": "production-verified" }
  ],
  "remediation_plan": {
    "ranked": [ { "rule":"R1", "weight":3.0, "resolution":"..." },
                { "rule":"R11","weight":0.6, "resolution":"..." } ],
    "resolution_set": ["...", "..."]
  },
  "unsubstantiated_claims": ["require-human-approval-for-irreversible-actions"],
  "cited_tips": ["allowlist-tools-per-agent-role", "..."],
  "trust_badges": [
    { "arsenal_tip_id": "allowlist-tools-per-agent-role", "verification_status": "community-reported", "enrichment_status": "draft" }
  ],
  "seal": null
}
```

`seal` is `null` in the expected fixtures; tests assert it separately (see §5).

---

## 2. Status definitions (WARN paragraph — authoritative)

- **FAIL** — any *required* (capability-inferred) rule is violated.
- **WARN** — all required gates pass, BUT the policy either (a) claims a tip it does not
  enforce (`unsubstantiated_claims` non-empty), or (b) makes one or more substantiated claims
  but **none** of those claimed tips is `production-verified` or `enrichment_status = reviewed`.
  A policy may cite draft/community guidance alongside reviewed or production-verified controls
  without being downgraded solely for that citation.
- **PASS** — all required gates pass, no unsubstantiated claims, and the policy either makes no
  claims or has at least one substantiated `production-verified`/`reviewed` claim.

Golden fixtures demonstrate **FAIL** (`fail.yaml`, `conflict.yaml`) and **PASS** (`pass.yaml`).
**WARN** is covered by a dedicated unit test (a policy that passes all gates but cites only a
theoretical tip).

---

## 3. Blast-radius weighting (data-driven, auditable)

Weight = `impact_rank(impact) × verification_conf(verification_status)`, read directly from each
tip's own fields in `data/tips.json`. When asked "where did the weights come from?" the answer
is: the dataset's own `impact` and `verification_status` values.

```
impact_rank:      transformative=4, high=3, medium=2, low=1
verification_conf: production-verified=1.0, lab-verified=0.8, community-reported=0.6, theoretical=0.3
```


[truncated — 5620 more characters]
```

### package.json

```
{
  "name": "arsenal-lint",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "packageManager": "pnpm@9.15.9",
  "engines": { "node": ">=22.0.0", "pnpm": ">=9.0.0" },
  "scripts": {
    "build": "tsc --noEmit",
    "test": "node --experimental-strip-types --test tests/*.test.ts",
    "arsenallint": "node --experimental-strip-types src/arsenallint-cli.ts",
    "demo": "node --experimental-strip-types src/arsenallint-cli.ts demo",
    "demo:ci": "ARSENAL_DATA_PATH=fixtures/catalog node --experimental-strip-types src/arsenallint-cli.ts demo"
  },
  "dependencies": {},
  "devDependencies": { "@types/node": "^22.15.0", "typescript": "^5.8.3" }
}

```

### examples/baseline-agent.ts

```typescript
defineAgent({ roles: [{ id: "executor", tools: ["inspect"] }], runtime: { max_steps: 50, retry_cap: 3 }, guardrails: { human_approval_for: ["apply_change"], kill_switch: true } });

```

### examples/unsafe-agent.ts

```typescript
defineAgent({ roles: [{ id: "executor", tools: ["inspect", "apply_change"], irreversible_tools: ["apply_change"] }], runtime: { max_steps: 50, retry_cap: 3 }, guardrails: { human_approval_for: [], kill_switch: true } });

```

### examples/hardened-agent.ts

```typescript
defineAgent({ roles: [{ id: "planner", tools: ["inspect", "propose"] }, { id: "executor", tools: ["apply_change", "inspect"], irreversible_tools: ["apply_change"] }], runtime: { max_steps: 50, wall_clock_timeout_sec: 300, token_budget: 200000, cost_budget_usd: 2, retry_cap: 3 }, guardrails: { human_approval_for: ["apply_change"], kill_switch: true, ssrf_guard: true, sandbox_code_exec: true } });

```

### fixtures/fail.yaml

```yaml
policy:
  name: "Bounded Multi-Tool Agent (broken)"
  version: "0.0.1"
  roles:
    - id: planner
      tools: [inspect, propose]
    - id: executor
      tools: [apply_change, inspect]
      irreversible_tools: [apply_change]
  runtime:
    max_steps: null
    wall_clock_timeout_sec: null
    token_budget: null
    cost_budget_usd: null
    retry_cap: null
  guardrails:
    human_approval_for: []
    ssrf_guard: false
    sandbox_code_exec: true
    kill_switch: false
  claims:
    - require-human-approval-for-irreversible-actions
  catalog_commit: "ca724eb19ae55efc04611817452b5c8e00ec9f2d"

```

### fixtures/conflict.yaml

```yaml
policy:
  name: "Broad Multi-Tool Agent"
  version: "0.9.0"
  roles:
    - id: planner
      tools: [inspect, propose, search, summarize, classify, extract, translate, draft]
    - id: executor
      tools: [apply_change, inspect, send, fetch, transform, validate, notify, archive, delete, merge, publish, export]
      irreversible_tools: [apply_change, delete]
  runtime:
    max_steps: 50
    wall_clock_timeout_sec: 300
    token_budget: 200000
    cost_budget_usd: 2.00
    retry_cap: 3
  guardrails:
    human_approval_for: []
    ssrf_guard: true
    sandbox_code_exec: true
    kill_switch: true
  claims: []
  catalog_commit: "ca724eb19ae55efc04611817452b5c8e00ec9f2d"

```

### fixtures/pass.yaml

```yaml
policy:
  name: "Bounded Multi-Tool Agent"
  version: "1.0.0"
  roles:
    - id: planner
      tools: [inspect, propose]
    - id: executor
      tools: [apply_change, inspect]
      irreversible_tools: [apply_change]
  runtime:
    max_steps: 50
    wall_clock_timeout_sec: 300
    token_budget: 200000
    cost_budget_usd: 2.00
    retry_cap: 3
  guardrails:
    human_approval_for: [apply_change]
    ssrf_guard: true
    sandbox_code_exec: true
    kill_switch: true
  claims:
    - require-human-approval-for-irreversible-actions
    - allowlist-tools-per-agent-role
    - add-a-max-step-budget-to-every-agent
    - keep-a-kill-switch-for-agent-actions
    - cap-agent-tool-retries
    - set-a-token-and-cost-budget-per-agent-run
  catalog_commit: "ca724eb19ae55efc04611817452b5c8e00ec9f2d"

```

### src/arsenallint-cli.ts

```typescript
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import { assertAttestableCapabilities, check, consistency, diff, digest, extract, fileDigest, generatedGuardSource, htmlReport, keygenFiles, loadCatalog, parsePolicyYaml, receipt, replayProof, sourceBinding, verifyReceipt } from "./arsenallint.ts";
const [cmd,...args]=process.argv.slice(2);const flag=(name:string)=>{const i=args.indexOf(name);return i<0?undefined:args[i+1];};const req=(name:string)=>{const v=flag(name);if(!v)throw new Error(`missing ${name}`);return v;};const policy=(p:string)=>parsePolicyYaml(readFileSync(p,"utf8"));const print=(x:unknown)=>console.log(JSON.stringify(x,null,2));const exitFor=(s:string,failWarn=false)=>s==="FAIL"?1:s==="WARN"&&failWarn?1:0;
try { if(cmd==="doctor"){const c=loadCatalog();print({ok:true,node:process.version,provenance:c.provenance});}
else if(cmd==="extract")print(extract(args[0]));
else if(cmd==="diff")print(diff(extract(args[0]),extract(args[1])));
else if(cmd==="check"){const p=policy(args[0]),c=loadCatalog(),v=check(p,c,args[0].replace(/^.*\//,"").replace(/\.yaml$/,"")),report=flag("--report");if(report){mkdirSync(dirname(report),{recursive:true});writeFileSync(report,htmlReport(v,[],undefined,c));}print(v);process.exitCode=exitFor(v.status,args.includes("--fail-on-warn"));}
else if(cmd==="guard"){const p=policy(args[0]),cap=extract(req("--source")),out=flag("--out")||"output/generated-guard.ts",c=loadCatalog();assertAttestableCapabilities(cap,p);mkdirSync(dirname(out),{recursive:true});writeFileSync(out,generatedGuardSource(p,digest(cap),c));print({guard:out});}
else if(cmd==="replay"){const p=policy(args[0]);print(replayProof(p,args[1]));}
else if(cmd==="keygen"){const dir=flag("--out")||"output/keys";mkdirSync(dir,{recursive:true});const keys=keygenFiles(dir,args.includes("--force"));print({public_key:keys.publicKeyPath});}
else if(cmd==="attest"){const p=policy(args[0]),source=req("--source"),trace=req("--trace"),cap=extract(source),catalog=loadCatalog(),verdict=check(p,catalog,"attest"),findings=consistency(p,cap);let capabilityError:string|undefined;try{assertAttestableCapabilities(cap,p);}catch(error){capabilityError=(error as Error).message;}if(verdict.status!=="PASS"||findings.length||capabilityError){print({verdict,consistency_findings:findings,capability_error:capabilityError,receipt:null});process.exitCode=1;}else{const replayResult=replayProof(p,trace),signed=receipt(p,cap,verdict,replayResult,readFileSync(req("--private-key"),"utf8"),catalog,sourceBinding(source).commit,fileDigest(source));const out=flag("--out")||"output/receipt.json";mkdirSync(dirname(out),{recursive:true});writeFileSync(out,JSON.stringify(signed,null,2));print({verdict,receipt:out});}}
else if(cmd==="verify"){const p=policy(req("--policy")),source=req("--source"),trace=flag("--trace")||"fixtures/traces/adversarial.jsonl";let cap:any,commit="unavailable",sourceDigest="unavailable",sourceError:string|undefined;try{cap=extract(source);sourceDigest=fileDigest(source);}catch(error){sourceError=`source capability input unavailable: ${(error as Error).message}`;cap={roles:[],runtime:{},guardrails:{},network_tools:[],code_exec_tools:[],approval_wrappers:[],evidence:[{file:source,line:0,kind:"unknown",detail:sourceError}],unsupported:[sourceError]};}try{commit=sourceBinding(source).commit;}catch(error){sourceError=sourceError||`source capability input unavailable: ${(error as Error).message}`;}const result=verifyReceipt(JSON.parse(readFileSync(args[0],"utf8")),readFileSync(req("--public-key"),"utf8"),{policy:p,capability:cap,replay:replayProof(p,trace),catalog:loadCatalog(),sourceCommit:commit,sourceDigest});if(sourceError)result.reasons.unshift(sourceError);print(result);process.exitCode=result.verified?0:1;}
else if(cmd==="gate"){const p=policy(req("--policy")),cap=extract(req("--source")),v=check(p,loadCatalog(),"gate"),f=consistency(p,cap);print({verdict:v,consistency_findings:f});process.exitCode=v.status==="PASS"&&!f.length?0:1;}
else if(cmd==="demo"){mkdirSync("output/demo-keys",{recursive:true});const p=policy("fixtures/pass.yaml"),c=loadCatalog(),v=check(p,c,"pass"),unsafe=extract("examples/unsafe-agent.ts"),cap=extract("examples/hardened-agent.ts"),t=replayProof(p,"fixtures/traces/adversarial.jsonl"),keys=keygenFiles("output/demo-keys",true),commit=sourceBinding("examples/hardened-agent.ts").commit,sourceDigest=fileDigest("examples/hardened-agent.ts"),signed=receipt(p,cap,v,t,readFileSync(keys.privateKeyPath,"utf8"),c,commit,sourceDigest),verified=verifyReceipt(signed,readFileSync(keys.publicKeyPath,"utf8"),{policy:p,capability:cap,replay:t,catalog:c,sourceCommit:commit,sourceDigest}),d=diff(unsafe,cap);mkdirSync("output",{recursive:true});writeFileSync("output/demo-receipt.json",JSON.stringify(signed,null,2));writeFileSync("output/arsenallint-report.html",htmlReport(v,t,cap,c,verified,d));print({verdict:v.status,diff:d,replay:t,receipt_verification:verified,report:"output/arsenallint-report.html"});}
else throw new Error("arsenallint doctor|extract|diff|check|guard|replay|keygen|attest|verify|gate|demo"); }catch(error){console.error((error as Error).message);process.exitCode=2;}

```

[5 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]