Files
prose-contracts/docs/AUTHORING-GUIDE.md
T
root 2ace79fcab
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 2s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 2s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 3s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 3s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 0s
no-mistakes(document): Update 5→6 node references and fix CT ID contradictions
2026-07-24 21:36:38 +00:00

309 lines
10 KiB
Markdown

# Prose Contract Authoring Guide
Approach this as the lead systems engineer at a company where every contract is a
production dependency — agents run from these every 15 minutes, every 6 hours,
every Sunday at 3am. A stale IP or a missing section isn't a typo; it's an
incident waiting to happen. Write every contract as if you'll be paged at 3am
when it breaks.
## Ground it in the subject
Before writing a single line of YAML, answer three questions and state them in
the description:
1. **What system does this contract touch?** Name the hosts, CTs, containers,
and services explicitly. "The inference fleet" is vague. "GPU .8 (RTX 3090,
qwen), .110 (RTX 5070, gemma), .15 (Strix Halo, ornith), and LiteLLM on CT
116" is specific.
2. **Who runs this contract, and when?** State the agent, the trigger (cron,
manual, event-driven), and the expected duration. "Runs every 6 hours via
cron on abiba, takes ~120 seconds for a fleet scan."
3. **What breaks if this contract is wrong?** Name the blast radius. "A stale
CT ID causes a restart of the wrong container. A wrong Grafana URL breaks
the monitoring dashboard for all agents."
If you cannot answer these three questions concretely, do not write the
contract yet — go verify against the live system first.
## Design principles
### Specificity over generality
A contract that says "check all Docker containers" is worse than one that lists
them by name. The list will go stale, but staleness is detectable (the CI lint
catches it). Vagueness is invisible and uncatchable. Be specific, accept that
specificity decays, and rely on the lint pipeline to catch the decay.
### Ground truth is the live system, not the contract
Every IP, port, hostname, and container name in a contract is a **live-state
field**. Mark them with `VERIFY-BEFORE-USE` in the access matrix. Before any
mutating operation, verify against the live system. The contract is a
hypothesis; the live system is the fact.
When you discover drift between contract and reality, do not "fix" the live
system to match the contract. Halt. Ask which is correct. Document the answer
in the contract and the knowledge graph. The `.117→.19` incident was caused by
fixing the wrong direction.
### Contracts are typed — respect the kind
| Kind | When to use | Must have | Must NOT have |
|------|------------|-----------|---------------|
| `function` | Stateless, callable helper | `## Parameters`, `## Returns`, `## Execution` | `## Maintains` (no state) |
| `responsibility` | Persistent duty with state | `## Maintains`, `## Continuity` or `## Execution` | — |
| `gateway` | External-driven entry point | `## Maintains` | `## Requires` |
| `pattern` | Reference/template/doctrine | Flexible — prose-driven | Don't `prose run` directly |
| `test` | Verification only | `## Execution` | Mutating operations |
Use `kind: architecture` sparingly — it's not a standard OpenProse kind. Most
architecture docs should be `kind: pattern`.
### One contract, one concern
A contract that monitors health AND deploys exporters AND sends alerts is three
contracts fighting for attention. Split them:
```
❌ infrastructure-monitoring.prose.md (deploy + export + alert)
✅ proxmox-monitor.prose.md (deploy exporters)
✅ gpu-monitor.prose.md (poll + dashboard)
✅ infrastructure-control.prose.md (reference topology)
```
The contract that deploys is a function. The contract that polls is a
responsibility. The contract that maps the topology is a pattern. One concern
per file, one kind per concern.
### Live-state fields are declared, not hidden
Don't bury IPs and hostnames in prose paragraphs. Put them in tables, mark them
with `VERIFY-BEFORE-USE`, and keep them in one place per contract:
```markdown
| Host | IP | Role | Trust |
|------|----|------|-------|
| CT 116 | 192.168.68.116 | LiteLLM + Grafana | ⚠️ VERIFY-BEFORE-USE |
```
The lint pipeline scans for known stale values. Tables are grep-able; prose is
not.
## Process: verify, draft, lint, review, ship
### Pass 1: Verify
Before writing, verify every live-state field you plan to include:
```bash
# Example: writing a contract about Zulip
curl -s http://192.168.68.19/api/v1/server_settings # is .19 reachable?
ssh root@192.168.68.19 "docker ps" # is Zulip in Docker?
pct config 117 | grep net0 # what IP does CT 117 have?
```
Write down the verified values. Use them in the contract. If you can't verify
something, mark it explicitly: `⚠️ UNVERIFIED — needs live check`.
### Pass 2: Draft
Write the contract following the structure for its kind. Fill every required
section. Use the templates below. Write descriptions that name the subject: "GPU
fleet health check across RTX 3090 (.8), RTX 5070 (.110), and Strix Halo (.15)"
not "monitoring check."
### Pass 3: Lint locally
Before pushing, run the same checks the CI will run:
```bash
bash scripts/prose-lint.sh
```
Fix everything that fails. The lint checks for:
- Valid YAML frontmatter
- Required sections per kind
- Regression patterns (/grafana/ route, CT 122/123)
- Missing Maintains/Parameters/Returns
### Pass 4: Self-critique
Read your contract and ask:
1. **Could an agent run this at 3am without context?** If it needs tribal
knowledge, add it to the description or a comment.
2. **Does any value contradict the infrastructure-control pattern?** Check CT
IDs, IPs, hostnames, nginx routes, Grafana access.
3. **Is anything in here that was previously fixed and reverted?** Check the
lint regression rules. If you're unsure, grep the git log for similar
changes.
4. **Am I saying the same thing as another contract?** If yes, link to it
instead of duplicating. One source of truth.
### Pass 5: Open PR, let CI review
Push to a branch, open a PR. The pipeline runs:
```
auth → validate → lint → ai-review → gate
```
The AI review compares your diff against the infrastructure-control ground
truth. It catches contradictions you might miss. If it flags something, don't
argue — verify and fix. The AI is reading the same ground truth you should have
read before writing.
## Restraint and self-critique
Spend your complexity budget in one place. A contract that deploys Prometheus,
configures Grafana, provisions dashboards, and sets up exporters is a function
that does four things — each of which can fail independently. Split them.
Don't add a `### Requires` that you haven't verified works. A dependency on
"SSH access to .116" is only valid if you've tested it from the agent that
will run the contract.
Cut sections that don't serve the runner. A 200-line appendix of curl commands
is documentation, not execution. Put it in a wiki or a README, not in the
contract. The contract is for the agent running it; the README is for the human
reading about it.
Before shipping, take one thing out. Like Chanel's advice: before leaving the
house, remove one accessory. Is there a section, a parameter, a check that
doesn't pull its weight? Cut it. The contract that ships with 5 sections will
be maintained. The one with 15 will rot.
## Writing in contracts
### Descriptions
A description is not a label. It's the first thing an agent reads before
deciding whether to run this contract. Make it answer: what does this do, to
what, and why should I care?
```
❌ "Monitors infrastructure health"
✅ "Scans all 6 Proxmox nodes and 19 CTs for disk pressure, checks Docker
container health on .7/.116/.17, alerts via Telegram DM on RED/CRITICAL"
```
### Field names
Name things by what they control, not by how the system is built:
```
❌ prometheus_scrape_interval_seconds
✅ check_every_seconds
```
The runner doesn't care that Prometheus is doing the scraping. They care how
often the check runs.
### Error messages and alerts
Errors tell the operator what went wrong and what to do about it. Be specific:
```
❌ "Health check failed"
✅ "GPU .15 (Strix Halo) unreachable at :8080 — firewalled to .116 only.
Check router /health/unified at http://192.168.68.116/health/unified instead."
```
### Comments
Comments in contracts explain WHY, not WHAT. The execution steps say what to
do. Comments say why we do it this way and not another:
```markdown
## Execution
1. **Check GPU health via router** — cannot probe .15:8080 directly
(firewalled to .116 only as of 2026-07-01 Vulkan rebuild)
2. **Skip sidecar check** — JSON exporters at :8090 were never deployed
on any GPU host; router falls back to GPU /health direct probe
```
## Templates
### Function contract template
```markdown
---
kind: function
name: contract-name
description: >
What this does, to what, and why. Be specific about hosts/services.
version: 1.0.0
---
## Parameters
- param_one: type — description (default: "value")
- param_two: type — description (default: "value")
## Returns
- result_field: type — description
## Requires
- Dependency one — how to verify it's available
- Dependency two
## Execution
1. **Step one** — what and why
2. **Step two** — what and why
3. **Compile and return** — assemble the output
```
### Responsibility contract template
```markdown
---
kind: responsibility
name: contract-name
description: >
What this recurring duty monitors/maintains. Name the systems, the
cadence, the blast radius.
version: 1.0.0
---
## Maintains
- state_field: { current: value, last_check: timestamp }
- another_field: description of maintained state
## Parameters
- param: type — description (default: "value")
## Requires
- Dependency one
- Dependency two
## Continuity
- Self-driven: check every N seconds/minutes/hours
- Also wakes on: trigger description
## Execution
1. **Check** — verify current state
2. **Evaluate** — compare against thresholds
3. **Remediate or escalate** — fix or alert
4. **Record** — update world-model state
```
## Enforcement
This guide is aspirational, not gated. The CI pipeline enforces structure
(frontmatter, required sections) and regression (known stale patterns). It
does not enforce prose quality or specificity. That's on you, the author.
Before you ship: read your contract as if you're the agent who will run it at
3am with 30 seconds of context. If you wouldn't trust it, rewrite it.