Skip to content

Commit 4a2cc36

Browse files
committed
docs(examples): add 4 more fictional policy examples with sample interactions
The single Acme example wasn't enough — different stacks need different policy shapes, and a Node.js shop's template doesn't help a Python data team or a Go microservices org. Adds four more, each with a section that's distinctive to its industry rather than just slot-filling the same template. New examples: - helio-python: Python 3.12 + Polars + DuckDB + FastAPI data/ML shop. Distinctive section: 'Reproducibility & Notebook Governance' — random seed discipline, dataset versioning, model cards, no production code in notebooks. - forge-go: Go 1.22 microservices shop on chi + sqlc + slog + OTel. Distinctive section: 'Concurrency & Error Discipline' — goroutine lifetime ownership, context propagation, init() ban, no panics in libraries, sentinel errors with errors.Is. - ledger-fintech: Java 21 + Spring Boot fintech under PCI-DSS, SOC 2, and GDPR. Distinctive shape: 'Regulatory Constraints' and 'PII Handling' lead the doc; the stack section is intentionally demoted because the compliance rules matter more than the library list. - minimal: Two-person product team with no formal sections at all — just a few paragraphs of 'we use X, we don't use Y, ask before adding anything.' Demonstrates that stackguard works on short policies, not just multi-section corporate ones. Every example file (including the existing acme one, which gains the section retroactively) ends with '## Sample stackguard interactions' showing 3-4 prompts and the exact renderer output: one HIGH-confidence block, one MEDIUM-confidence flag, one PASS, and one LOW-confidence soft warning. The outputs match the real renderer's format so users can compare the demo with reality. Adds examples/README.md as an index with a 'pick one based on your stack' table, copy-paste --policy commands for trying each without committing, and a list of what makes a realistic policy doc the checker can actually match against.
1 parent 24da943 commit 4a2cc36

6 files changed

Lines changed: 810 additions & 0 deletions

examples/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Example policies
2+
3+
These are fictional engineering policy documents you can use as a
4+
starting point for your own. They're written to be **specific enough
5+
that stackguard can actually flag prompts against them** — vague
6+
guidelines produce vague checks.
7+
8+
Pick the one closest to your stack, copy it into your repo as
9+
`ENGINEERING_GUIDELINES.md` (or whatever you call it), and edit. Each
10+
file is short enough that adapting it to your real stack is a 30-minute
11+
exercise, not a multi-week project.
12+
13+
| File | Industry | Stack | Distinctive section |
14+
|---|---|---|---|
15+
| [`policy.example.md`](./policy.example.md) | Generic SaaS | Node.js + TypeScript + Postgres + Next.js | Standard structure (good default) |
16+
| [`policy.helio-python.example.md`](./policy.helio-python.example.md) | Data / ML | Python 3.12 + Polars + DuckDB + FastAPI + PyTorch | Reproducibility & Notebook Governance |
17+
| [`policy.forge-go.example.md`](./policy.forge-go.example.md) | Microservices | Go 1.22 + chi + sqlc + slog + OTel | Concurrency & Error Discipline |
18+
| [`policy.ledger-fintech.example.md`](./policy.ledger-fintech.example.md) | Fintech (regulated) | Java 21 + Spring Boot + audit-logged Postgres + Vault | Regulatory Constraints + PII Handling |
19+
| [`policy.minimal.example.md`](./policy.minimal.example.md) | Two-person product team | Whatever's small | Deliberately tiny — no formal sections |
20+
21+
## Picking one
22+
23+
- **You work at a Node.js shop and want a structured template.** Start with `policy.example.md`. It's the most generic and the easiest to adapt.
24+
- **You're a data team.** Start with `policy.helio-python.example.md`. The reproducibility section is the part that matters most and isn't in the others.
25+
- **You're a Go shop.** Start with `policy.forge-go.example.md`. The concurrency rules are Go-specific and worth keeping.
26+
- **You handle money, PII, or anything with a regulatory footprint.** Start with `policy.ledger-fintech.example.md`. The compliance framing and PII section don't appear in the other examples.
27+
- **You're 1–3 people and don't want to read or write a long doc.** Start with `policy.minimal.example.md`. Two paragraphs of "we use X, we don't use Y" is enough for stackguard to enforce.
28+
29+
## Trying one without committing
30+
31+
Every example file is self-contained, so you can point stackguard at it
32+
directly without `init`-ing a project:
33+
34+
```bash
35+
# From the stackguard repo root
36+
stackguard check "add a MongoDB connection" \
37+
--policy ./examples/policy.example.md
38+
39+
stackguard check "load this CSV with pandas" \
40+
--policy ./examples/policy.helio-python.example.md
41+
42+
stackguard check "use gin for the HTTP server" \
43+
--policy ./examples/policy.forge-go.example.md
44+
45+
stackguard check "log the user's email when login fails" \
46+
--policy ./examples/policy.ledger-fintech.example.md \
47+
--mode block
48+
49+
stackguard check "use lodash.debounce on the search input" \
50+
--policy ./examples/policy.minimal.example.md
51+
```
52+
53+
Each example file has a `## Sample stackguard interactions` section at
54+
the bottom showing what the response looks like for prompts the policy
55+
flags and prompts it doesn't.
56+
57+
## What makes these realistic
58+
59+
- **Specific package names.** "Use `@acme/db`, never use `prisma` or
60+
`drizzle`" is something stackguard can match. "Prefer simple
61+
abstractions" is not.
62+
- **Explicit reasons in the prose.** When a rule has a sentence
63+
explaining *why*, the model can quote it back to the developer in
64+
the violation message. That's much more persuasive than "rule 4.2.1."
65+
- **A mix of prohibitions and approvals.** Prohibitions tell the AI
66+
what to avoid; approvals tell it what to reach for instead. Both
67+
matter — without the approval list, the suggested revision is just
68+
"don't do that."
69+
- **An "AI-Specific Rules" section.** This is the section the checker
70+
pays the most attention to, because it's framed as instructions to
71+
an AI assistant. Putting your most-violated rules here is the
72+
highest-leverage thing you can do to improve precision.
73+
74+
## When *not* to use these as your policy
75+
76+
These are fictional. The libraries, package names, and version
77+
numbers were chosen to make the examples self-contained and
78+
unambiguous, not because they reflect any real company's choices.
79+
Don't ship `@acme/auth` to your team and assume they know what it is.
80+
Adapt the structure, then write your own.

examples/policy.example.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,83 @@ When prompting an AI coding assistant, do NOT ask it to:
9494
When in doubt, ask the AI to use the approved stack above.
9595
If you genuinely need an exception, file an RFC with @platform-eng
9696
before writing the prompt.
97+
98+
---
99+
100+
## Sample stackguard interactions
101+
102+
Run them yourself with:
103+
104+
```bash
105+
stackguard check "<prompt>" --policy ./policy.example.md
106+
```
107+
108+
### Example 1 — explicit prohibited tech (HIGH confidence block)
109+
110+
```
111+
$ stackguard check "add a MongoDB connection for user sessions"
112+
113+
⚠ stackguard: guideline conflict detected
114+
──────────────────────────────────────────────────────────────────────
115+
116+
"add a MongoDB connection for user sessions"
117+
Rule: NEVER use MongoDB, DynamoDB, or any NoSQL store for primary
118+
data.
119+
Why: The prompt explicitly names MongoDB; Acme uses PostgreSQL
120+
via @acme/db.
121+
Level: HIGH confidence
122+
123+
Suggested revision:
124+
┌──────────────────────────────────────────────────────────┐
125+
│ Add a Postgres-backed user_sessions table accessed via │
126+
│ @acme/db, with a parameterized query for lookups │
127+
└──────────────────────────────────────────────────────────┘
128+
129+
[P]roceed anyway [R]evise [S]how policy [C]ancel
130+
```
131+
132+
### Example 2 — custom security primitive (HIGH confidence block)
133+
134+
```
135+
$ stackguard check "implement JWT auth from scratch with refresh tokens"
136+
137+
⚠ stackguard: guideline conflict detected
138+
──────────────────────────────────────────────────────────────────────
139+
140+
"implement JWT auth from scratch with refresh tokens"
141+
Rule: NEVER implement JWT signing, verification, or refresh logic
142+
from scratch. @acme/auth handles token lifecycle.
143+
Why: The prompt asks for custom token logic, which the policy
144+
delegates to the shared auth wrapper.
145+
Level: HIGH confidence
146+
147+
Suggested revision:
148+
┌──────────────────────────────────────────────────────────┐
149+
│ Add login and session handling using @acme/auth, which │
150+
│ manages token issuance and refresh │
151+
└──────────────────────────────────────────────────────────┘
152+
153+
[P]roceed anyway [R]evise [S]how policy [C]ancel
154+
```
155+
156+
### Example 3 — vague prompt (PASSES)
157+
158+
```
159+
$ stackguard check "build a settings page where users can change their display name"
160+
✓ stackguard: ok
161+
```
162+
163+
The prompt doesn't name a banned library or pattern. The AI may
164+
use approved tools in its response — that's fine.
165+
166+
### Example 4 — soft warning (LOW confidence, passes through)
167+
168+
```
169+
$ stackguard check "add a date picker that handles timezones nicely"
170+
ℹ stackguard: possible conflict (low confidence — passing through)
171+
"date picker that handles timezones" may conflict with "moment is prohibited…"
172+
```
173+
174+
The model thinks this *might* drift toward `moment-timezone` but
175+
isn't sure. Per ADR-002, low-confidence violations don't interrupt
176+
the developer.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Forge Systems Engineering Guidelines
2+
3+
**Owner:** Platform Engineering (@platform)
4+
**Version:** 7.2
5+
**Last updated:** 2026-02-14
6+
**Enforcement:** Pre-prompt via `stackguard`, plus `golangci-lint` + a custom `forge-lint` analyzer in CI. Hard violations require an architecture review.
7+
8+
---
9+
10+
## 1. Approved Tech Stack
11+
12+
### Language and runtime
13+
- **Go 1.22+** (we depend on `slog`, `range over int`, and `for-range` loop variable scoping)
14+
- **Modules pinned to exact versions.** No `^` or wildcard ranges in go.mod.
15+
16+
### HTTP services
17+
- **Standard library `net/http`** with the `chi` router for routing only
18+
- **NOT** gin, echo, fiber, gorilla/mux, or any other framework
19+
- **NOT** gRPC for new services (existing gRPC services stay; new work is REST + protobuf payloads)
20+
21+
### Persistence
22+
- **Postgres 16** via **`sqlc`-generated** typed queries
23+
- **NEVER use** `database/sql` directly outside `sqlc` output
24+
- **NEVER use** an ORM. We have specifically rejected GORM, ent, bun, and xorm.
25+
26+
### Logging, metrics, tracing
27+
- **`log/slog`** (stdlib) — NOT logrus, NOT zap, NOT zerolog
28+
- **OpenTelemetry SDK** for traces and metrics
29+
- **`prometheus/client_golang`** for metric exposition
30+
- All three are wired through `forge/observability`. Use that, not the libs directly.
31+
32+
### Internal packages
33+
- `forge/auth` — request authentication and authorization
34+
- `forge/config` — typed config from env via `envconfig`-style struct tags
35+
- `forge/observability` — logger, tracer, meter
36+
- `forge/db` — sqlc helpers, migrations, connection pooling
37+
38+
---
39+
40+
## 2. Prohibited Libraries
41+
42+
| Prohibited | Use instead |
43+
|---|---|
44+
| `gin`, `echo`, `fiber`, `gorilla/mux` | `net/http` + `chi` router |
45+
| `gorm`, `ent`, `bun`, `xorm`, `sqlx` | `sqlc` |
46+
| `logrus`, `zap`, `zerolog`, `glog` | `log/slog` |
47+
| `viper`, `koanf` | `forge/config` |
48+
| `errors/pkg` | stdlib `errors` (1.20+ has wrapping) |
49+
| `uuid`-the-package | `crypto/rand` (we generate ULIDs in `forge/ids`) |
50+
| any third-party HTTP client | stdlib `net/http.Client` with our `forge/httpclient` wrapper |
51+
52+
---
53+
54+
## 3. Concurrency & Error Discipline
55+
56+
(Unique to Forge — Go gives you enough rope to hang yourself with goroutines. These rules exist because we have spent real on-call hours debugging the mistakes they prevent.)
57+
58+
### Goroutines
59+
- **Every `go` statement must have an obvious lifetime owner.** Either it's tied to a `context.Context` that someone will cancel, or it's tied to a `sync.WaitGroup` someone will `Wait()` on. Fire-and-forget goroutines are banned. We have an analyzer that fails CI on bare `go func()` outside `main`.
60+
- **No goroutines inside HTTP handlers** unless explicitly cancelled by the request context. Background work belongs in a worker pool, not in a handler.
61+
- **`errgroup.Group` is the default** for fan-out; raw goroutines + channels require a justification in the PR description.
62+
63+
### Context
64+
- **`context.Context` is the FIRST argument** of any function that does I/O, makes a network call, or runs longer than a microsecond. No exceptions.
65+
- **NEVER store a context in a struct.** Pass it explicitly.
66+
- **NEVER use `context.Background()` outside `main` and tests.** Use the context you were handed.
67+
68+
### Errors
69+
- **Wrap with `%w`, never with `%v`** when you want the chain preserved. `forge-lint` enforces this.
70+
- **NEVER use `panic` in library code.** Panics are for "this state is impossible" — they belong in `main` or in test setup, not in production paths.
71+
- **Sentinel errors must be exported** so callers can `errors.Is` them. Don't compare error strings.
72+
73+
### Globals and init
74+
- **`init()` is banned** outside generated code (sqlc, protoc). Side effects at import time are how we get unexplained 90-second test startup times.
75+
- **No package-level mutable state.** Configuration is injected explicitly via constructors. The compiler can't help you debug mutable globals.
76+
77+
---
78+
79+
## 4. AI-Specific Rules
80+
81+
When prompting an AI coding assistant, do NOT ask it to:
82+
83+
- Use gin, echo, fiber, or any router/framework other than chi
84+
- Use GORM, ent, bun, or any ORM
85+
- Use logrus, zap, or zerolog
86+
- Spawn a goroutine without a clear lifetime owner
87+
- Write `init()` functions
88+
- Use `panic` in library code
89+
- Add a third-party HTTP client (axios-equivalents like resty are banned)
90+
- Bypass `forge/observability`, `forge/config`, `forge/auth`, or `forge/db`
91+
- Use `context.Background()` outside `main`
92+
93+
---
94+
95+
## Sample stackguard interactions
96+
97+
Run them yourself with:
98+
99+
```bash
100+
stackguard check "<prompt>" --policy ./policy.forge-go.example.md
101+
```
102+
103+
### Example 1 — explicit framework reference (HIGH confidence block)
104+
105+
```
106+
$ stackguard check "set up a gin server with a /healthz endpoint"
107+
108+
⚠ stackguard: guideline conflict detected
109+
──────────────────────────────────────────────────────────────────────
110+
111+
"set up a gin server with a /healthz endpoint"
112+
Rule: gin is prohibited; use net/http + chi router
113+
Why: The prompt explicitly names gin, which is on the prohibited
114+
list. Forge uses stdlib net/http with chi for routing.
115+
Level: HIGH confidence
116+
117+
Suggested revision:
118+
┌──────────────────────────────────────────────────────────┐
119+
│ Set up a net/http server with a chi router and a │
120+
│ /healthz endpoint │
121+
└──────────────────────────────────────────────────────────┘
122+
123+
[P]roceed anyway [R]evise [S]how policy [C]ancel
124+
```
125+
126+
### Example 2 — concurrency discipline (MEDIUM confidence)
127+
128+
```
129+
$ stackguard check "fire off a background goroutine to email the user after the response"
130+
131+
⚠ stackguard: guideline conflict detected
132+
──────────────────────────────────────────────────────────────────────
133+
134+
"fire off a background goroutine to email the user after the response"
135+
Rule: No goroutines inside HTTP handlers unless explicitly cancelled
136+
by the request context. Background work belongs in a worker
137+
pool, not in a handler.
138+
Why: The prompt asks for a fire-and-forget goroutine in a request
139+
handler, which Forge bans because of past production incidents.
140+
Level: MEDIUM confidence
141+
142+
Suggested revision:
143+
┌──────────────────────────────────────────────────────────┐
144+
│ Enqueue an email job onto the worker pool after the │
145+
│ response so it runs outside the request lifetime │
146+
└──────────────────────────────────────────────────────────┘
147+
148+
[P]roceed anyway [R]evise [S]how policy [C]ancel
149+
```
150+
151+
### Example 3 — vague prompt (PASSES)
152+
153+
```
154+
$ stackguard check "add a function that parses an ISO8601 timestamp into time.Time"
155+
✓ stackguard: ok
156+
```
157+
158+
The prompt doesn't name a banned library or pattern. The `time` package
159+
is stdlib and unrestricted.
160+
161+
### Example 4 — ORM mention (HIGH confidence block)
162+
163+
```
164+
$ stackguard check "add a User model with GORM and a hasMany relation to Posts"
165+
166+
⚠ stackguard: guideline conflict detected
167+
──────────────────────────────────────────────────────────────────────
168+
169+
"add a User model with GORM and a hasMany relation to Posts"
170+
Rule: NEVER use an ORM. We have specifically rejected GORM, ent,
171+
bun, and xorm.
172+
Why: The prompt names GORM directly. Forge uses sqlc-generated
173+
typed queries instead of an ORM abstraction.
174+
Level: HIGH confidence
175+
176+
Suggested revision:
177+
┌──────────────────────────────────────────────────────────┐
178+
│ Add a users table migration and write a sqlc query │
179+
│ that joins users to posts │
180+
└──────────────────────────────────────────────────────────┘
181+
182+
[P]roceed anyway [R]evise [S]how policy [C]ancel
183+
```

0 commit comments

Comments
 (0)