Skip to content
Merged
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
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# Changelog

## Unreleased

**Phase 1: JSON Contracts Foundation**

### Breaking Changes

- JSON output now nests command-specific data under a `data` field instead of flattening at the top level
- Added new envelope fields: `tool_version`, `generated_at`, `severity`
- Consumers must update to read from `response.data` instead of directly from `response`

### New Commands

- **`pgcrate context`**: Connection context, server info, extensions, and privileges
- **`pgcrate capabilities`**: Permission-aware feature discovery

### JSON Envelope v2.0.0

All diagnostic commands now use a consistent envelope:

```json
{
"ok": true,
"schema_id": "pgcrate.diagnostics.triage",
"schema_version": "2.0.0",
"tool_version": "0.3.0",
"generated_at": "2026-01-19T12:00:00Z",
"severity": "healthy",
"warnings": [],
"errors": [],
"data": { ... }
}
```

### Reason Codes

- Added 27 stable reason codes across 3 categories (operational, policy, capability)
- Skipped checks now use `reason_code` from the ReasonCode enum for automation
- Error responses use the same envelope structure as success responses

### Improvements

- `data_directory` in context output now gated behind `--no-redact` flag
- SQLSTATE 57014 correctly disambiguates `statement_timeout` vs `query_cancelled`
- `indexes` command returns `severity: warning` when findings exist (was always `healthy`)
- JSON schemas use `$ref` composition with `envelope.schema.json` for consistency

## v0.3.0

Production-safe diagnostic commands with safety rails.
Expand Down
49 changes: 45 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ pgcrate extension list # Installed extensions
pgcrate extension list --available # Extensions available to install
```

### Diagnostics

Agent-friendly health checks with JSON output for automation:

```bash
pgcrate triage # Quick health overview (locks, xid, sequences)
pgcrate triage --json # Machine-readable JSON output
pgcrate context --json # Connection context, server info, privileges
pgcrate capabilities --json # What can this connection do?
pgcrate locks # Blocking locks and long transactions
pgcrate xid # Transaction ID wraparound analysis
pgcrate sequences # Sequence exhaustion check
pgcrate indexes # Missing, unused, duplicate indexes
```

All diagnostic commands support timeout flags for production safety:
- `--connect-timeout <ms>` - Connection timeout (default: 5000ms)
- `--statement-timeout <ms>` - Query timeout (default: 30000ms)
- `--lock-timeout <ms>` - Lock wait timeout (default: 500ms)

### Data Operations

```bash
Expand All @@ -145,17 +165,31 @@ pgcrate doctor # Health checks for CI

### CI/CD Integration

Query commands support `--json` for machine-readable output:
Commands support `--json` for machine-readable output with versioned schemas:

```bash
pgcrate migrate status --json # JSON migration status
pgcrate doctor --json # JSON health report
pgcrate triage --json # JSON health check with severity
pgcrate context --json # JSON connection/server info
pgcrate capabilities --json # JSON capability discovery
pgcrate diff --from $PROD --to $DEV --json # JSON diff
pgcrate snapshot list --json # JSON snapshot list
pgcrate snapshot info dev --json # JSON snapshot details
```

Exit codes: `0` = success, `1` = action needed (e.g., pending migrations), `2` = error.
JSON output uses a consistent envelope:
```json
{
"ok": true,
"schema_id": "pgcrate.diagnostics.triage",
"schema_version": "2.0.0",
"tool_version": "0.3.0",
"generated_at": "2026-01-19T12:00:00Z",
"severity": "warning",
"data": { ... }
}
```

Exit codes for diagnostics: `0` = healthy, `1` = warning, `2` = critical, `10+` = operational failure.

## Why pgcrate?

Expand Down Expand Up @@ -231,6 +265,13 @@ DROP TABLE users;
| `pgcrate sql` | Run ad-hoc SQL (alias: `query`) |
| `pgcrate seed <cmd>` | List, run, validate, or diff seed data |
| `pgcrate model <cmd>` | Run, compile, test, lint, graph, new, or show models |
| `pgcrate triage` | Quick health check (locks, xid, sequences) |
| `pgcrate context` | Connection context, server info, privileges |
| `pgcrate capabilities` | What can this connection do? |
| `pgcrate locks` | Blocking locks and long transactions |
| `pgcrate xid` | Transaction ID wraparound analysis |
| `pgcrate sequences` | Sequence exhaustion check |
| `pgcrate indexes` | Missing, unused, duplicate indexes |
| `pgcrate doctor` | Run health checks |
| `pgcrate bootstrap` | Setup environment with anonymized data from source |
| `pgcrate snapshot <cmd>` | Save (with profiles), restore, list, or delete snapshots |
Expand Down
145 changes: 145 additions & 0 deletions schemas/action.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "pgcrate.diagnostics.action",
"title": "pgcrate action schema",
"description": "Schema for structured actions in diagnostic outputs (used with --include-fixes)",
"type": "object",
"additionalProperties": false,
"required": [
"action_id",
"action_type",
"command",
"args",
"command_string",
"description",
"available",
"mutates",
"risk",
"gates"
],
"properties": {
"action_id": {
"type": "string",
"pattern": "^[a-z0-9-]+",
"description": "Unique action identifier"
},
"action_type": {
"$ref": "#/$defs/actionType"
},
"command": {
"type": "string",
"description": "Command to run (e.g., pgcrate)"
},
"args": {
"type": "array",
"items": { "type": "string" },
"description": "Command arguments"
},
"command_string": {
"type": "string",
"description": "Full command string for display"
},
"description": {
"type": "string",
"description": "Human-readable description of what this action does"
},
"requires_capability_id": {
"type": "string",
"description": "Which capability is required"
},
"available": {
"type": "boolean",
"description": "Whether this action is available in the current context"
},
"unavailable_reason": {
"type": "string",
"description": "Reason why action is unavailable"
},
"mutates": {
"type": "boolean",
"description": "Whether this action mutates data"
},
"sql_preview": {
"type": "array",
"items": { "type": "string" },
"description": "SQL statements that would be executed"
},
"lock_risk": {
"$ref": "#/$defs/lockRisk"
},
"risk": {
"type": "string",
"enum": ["none", "low", "medium", "high", "extreme"],
"description": "Overall risk level"
},
"evidence": {
"type": "object",
"description": "Evidence that led to this recommendation"
},
"gates": {
"$ref": "#/$defs/actionGates"
},
"verify": {
"type": "array",
"items": { "$ref": "#/$defs/verifyStep" },
"description": "Steps to verify success after execution"
}
},
"$defs": {
"actionType": {
"type": "string",
"enum": ["investigate", "fix", "monitor"],
"description": "Type of action"
},
"lockRisk": {
"type": "string",
"enum": ["none", "low", "medium", "high", "extreme"],
"description": "Risk level for lock acquisition"
},
"actionGates": {
"type": "object",
"additionalProperties": false,
"properties": {
"requires_write": {
"type": "boolean",
"description": "Requires --read-write mode"
},
"requires_primary": {
"type": "boolean",
"description": "Requires --primary flag"
},
"requires_confirmation": {
"type": "boolean",
"description": "Requires --yes flag for confirmation"
},
"requires_capability": {
"type": "string",
"description": "Required capability ID"
},
"min_pg_version": {
"type": "integer",
"description": "Minimum PostgreSQL version required"
}
}
},
"verifyStep": {
"type": "object",
"additionalProperties": false,
"required": ["description", "command", "expected"],
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of what to verify"
},
"command": {
"type": "string",
"description": "Command to run for verification"
},
"expected": {
"type": "string",
"description": "Expected outcome"
}
}
}
}
}
111 changes: 111 additions & 0 deletions schemas/capabilities.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "pgcrate.diagnostics.capabilities",
"title": "pgcrate capabilities output",
"description": "Available capabilities based on privileges and connection mode",
"allOf": [
{ "$ref": "envelope.schema.json" }
],
"properties": {
"schema_id": { "const": "pgcrate.diagnostics.capabilities" },
"data": { "$ref": "#/$defs/capabilitiesData" }
},
"$defs": {
"capabilitiesData": {
"type": "object",
"additionalProperties": false,
"required": ["capabilities", "summary"],
"properties": {
"capabilities": {
"type": "array",
"items": { "$ref": "#/$defs/capabilityInfo" },
"description": "List of all capabilities and their status"
},
"summary": { "$ref": "#/$defs/capabilitySummary" }
}
},
"capabilityInfo": {
"type": "object",
"additionalProperties": false,
"required": ["id", "name", "description", "status", "requirements"],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z]+\\.[a-z_]+$",
"description": "Capability identifier (e.g., diagnostics.triage)"
},
"name": {
"type": "string",
"description": "Human-readable name"
},
"description": {
"type": "string",
"description": "Brief description of the capability"
},
"status": { "$ref": "#/$defs/capabilityStatus" },
"reasons": {
"type": "array",
"items": { "$ref": "envelope.schema.json#/$defs/reasonInfo" },
"description": "Reasons for degraded/unavailable status"
},
"requirements": {
"type": "array",
"items": { "$ref": "#/$defs/requirement" },
"description": "Requirements and their status"
},
"limitations": {
"type": "array",
"items": { "type": "string" },
"description": "Limitations when degraded"
}
}
},
"capabilityStatus": {
"type": "string",
"enum": ["available", "degraded", "unavailable", "unknown"],
"description": "Availability status of the capability"
},
"requirement": {
"type": "object",
"additionalProperties": false,
"required": ["what", "met"],
"properties": {
"what": {
"type": "string",
"description": "What is required"
},
"met": {
"type": "boolean",
"description": "Whether the requirement is met"
}
}
},
"capabilitySummary": {
"type": "object",
"additionalProperties": false,
"required": ["available", "degraded", "unavailable", "unknown"],
"properties": {
"available": {
"type": "integer",
"minimum": 0,
"description": "Count of available capabilities"
},
"degraded": {
"type": "integer",
"minimum": 0,
"description": "Count of degraded capabilities"
},
"unavailable": {
"type": "integer",
"minimum": 0,
"description": "Count of unavailable capabilities"
},
"unknown": {
"type": "integer",
"minimum": 0,
"description": "Count of capabilities with unknown status"
}
}
}
}
}
Loading