Initial Commit
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
# OpenSpec Change: enterprise-network-governance
|
||||
|
||||
- **Change ID**: enterprise-network-governance
|
||||
- **Package**: `@suaveplan/network-governance`
|
||||
- **Category**: universal/network
|
||||
- **Target path**: `universal/network/network-governance`
|
||||
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
|
||||
- **Date**: 2026-04-24
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/network-governance` on the genesis branch from scratch at enterprise quality. The package provides network policy governance: define and enforce rate limits, access control policies (IP allowlist/denylist, peer authentication, capability gating), connection quotas, and audit trails for policy decisions. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for all policy types, OTel instrumentation on policy evaluations, `AbortSignal` on async operations, injectable `Clock` for time-window rate limiting, event-bus emission for policy violations, and property-based tests for policy evaluation invariants.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — this is a fresh build.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | Base class for `NetworkGovernanceError`; stable error codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike` |
|
||||
| `@suaveplan/events` (`workspace:^`) | Event bus for policy violation events |
|
||||
| `@suaveplan/logger` (`workspace:^`) | Structured logging with audit trail |
|
||||
| `@suaveplan/rules` (`workspace:^`) | Business rules engine for policy evaluation |
|
||||
| `zod` (`catalog:forms`) | Zod schemas |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Spans + counters on policy evaluation |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`).
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
### Core
|
||||
|
||||
```ts
|
||||
function createGovernanceEngine(options?: GovernanceEngineOptions): GovernanceEngine
|
||||
|
||||
interface GovernanceEngine {
|
||||
addPolicy(policy: NetworkPolicy): void
|
||||
removePolicy(policyId: string): void
|
||||
evaluate(request: GovernanceRequest, signal?: AbortSignal): Promise<GovernanceDecision>
|
||||
getAuditLog(limit?: number): ReadonlyArray<AuditEntry>
|
||||
on(event: GovernanceEvent, handler: (entry: AuditEntry) => void): () => void
|
||||
}
|
||||
```
|
||||
|
||||
### Policy types
|
||||
|
||||
```ts
|
||||
type NetworkPolicy =
|
||||
| RateLimitPolicy
|
||||
| AccessControlPolicy
|
||||
| QuotaPolicy
|
||||
| CapabilityPolicy
|
||||
|
||||
interface BasePolicy {
|
||||
id: string
|
||||
name: string
|
||||
priority: number
|
||||
action: 'allow' | 'deny' | 'throttle'
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
interface RateLimitPolicy extends BasePolicy {
|
||||
type: 'rate-limit'
|
||||
maxRequests: number
|
||||
windowMs: number
|
||||
keyBy: 'peer-id' | 'ip' | 'user-id' | string
|
||||
}
|
||||
|
||||
interface AccessControlPolicy extends BasePolicy {
|
||||
type: 'access-control'
|
||||
allowlist?: string[]
|
||||
denylist?: string[]
|
||||
requireAuth?: boolean
|
||||
}
|
||||
|
||||
interface QuotaPolicy extends BasePolicy {
|
||||
type: 'quota'
|
||||
maxConnections?: number
|
||||
maxBandwidthBps?: number
|
||||
resetIntervalMs?: number
|
||||
}
|
||||
|
||||
interface CapabilityPolicy extends BasePolicy {
|
||||
type: 'capability'
|
||||
requiredCapabilities: string[]
|
||||
target?: string // endpoint or channel pattern
|
||||
}
|
||||
```
|
||||
|
||||
### Evaluation types
|
||||
|
||||
```ts
|
||||
interface GovernanceRequest {
|
||||
peerId?: string
|
||||
ip?: string
|
||||
userId?: string
|
||||
capabilities?: string[]
|
||||
target?: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
interface GovernanceDecision {
|
||||
readonly allowed: boolean
|
||||
readonly reason?: string
|
||||
readonly appliedPolicies: ReadonlyArray<string>
|
||||
readonly throttleMs?: number
|
||||
}
|
||||
|
||||
interface AuditEntry {
|
||||
readonly timestamp: number
|
||||
readonly peerId?: string
|
||||
readonly decision: GovernanceDecision
|
||||
readonly policyId: string
|
||||
readonly type: GovernanceEvent
|
||||
}
|
||||
|
||||
type GovernanceEvent = 'allowed' | 'denied' | 'throttled' | 'quota-exceeded'
|
||||
|
||||
interface GovernanceEngineOptions {
|
||||
logger?: LoggerLike
|
||||
clock?: Clock
|
||||
eventBus?: EventBusLike
|
||||
auditLogSize?: number
|
||||
}
|
||||
```
|
||||
|
||||
### Error codes
|
||||
|
||||
```ts
|
||||
type NetworkGovernanceErrorCode =
|
||||
| 'GOVERNANCE_POLICY_NOT_FOUND'
|
||||
| 'GOVERNANCE_EVALUATION_FAILED'
|
||||
| 'GOVERNANCE_INVALID_POLICY'
|
||||
| 'GOVERNANCE_ABORT'
|
||||
|
||||
function registerNetworkGovernanceErrorCodes(): void
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Genesis additions
|
||||
|
||||
1. **Structured `NetworkGovernanceError`** with stable `GOVERNANCE.*` codes
|
||||
2. **Zod schemas** for all policy types and `GovernanceRequest`
|
||||
3. **Rate limiting with sliding window** — injectable `Clock` for time-window bounds
|
||||
4. **Audit log** with bounded ring buffer (`auditLogSize`, default 1000)
|
||||
5. **Policy priority ordering** — higher-priority policies evaluated first; first deny wins
|
||||
6. **Event-bus violation emission** — fires `governance.denied`, `governance.throttled`
|
||||
7. **OTel instrumentation** — span `governance.evaluate`, counter `governance.decisions_total`, attributes `decision.allowed`, `policy_id`
|
||||
8. **Property-based tests** — deny wins over allow when both match same request; rate limit counter monotonically increases per key
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
universal/network/network-governance/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
constants/
|
||||
index.ts
|
||||
types/
|
||||
common.ts
|
||||
common.md
|
||||
core/
|
||||
engine/
|
||||
engine.ts
|
||||
engine.md
|
||||
engine.types.ts
|
||||
engine.test.ts
|
||||
index.ts
|
||||
policies/
|
||||
rate-limit/
|
||||
rate-limit.ts
|
||||
rate-limit.md
|
||||
rate-limit.test.ts
|
||||
index.ts
|
||||
access-control/
|
||||
access-control.ts
|
||||
access-control.md
|
||||
access-control.test.ts
|
||||
index.ts
|
||||
quota/
|
||||
quota.ts
|
||||
quota.md
|
||||
quota.test.ts
|
||||
index.ts
|
||||
capability/
|
||||
capability.ts
|
||||
capability.md
|
||||
capability.test.ts
|
||||
index.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
property.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/rules` must be published.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Tasks: enterprise-network-governance
|
||||
|
||||
**Change ID**: enterprise-network-governance
|
||||
**Package**: `@suaveplan/network-governance`
|
||||
**Target path**: `universal/network/network-governance`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/rules`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] Create `universal/network/network-governance/` with `src/`, `tests/`, and all feature subdirectories.
|
||||
- [x] Write `package.json` at version `0.1.0`: `"sideEffects": false`, exports map, deps per `proposal.md`.
|
||||
- [x] Write `tsconfig.json`, `vitest.config.ts`, scripts.
|
||||
|
||||
---
|
||||
|
||||
## 2. Types and error codes
|
||||
|
||||
- [x] Define all public types in `src/types/common.ts`: `NetworkPolicy`, `RateLimitPolicy`, `AccessControlPolicy`, `QuotaPolicy`, `CapabilityPolicy`, `GovernanceRequest`, `GovernanceDecision`, `AuditEntry`, `GovernanceEvent`, `GovernanceEngineOptions`.
|
||||
- [x] Write `src/types/common.md` (≥200 words, simple + advanced examples).
|
||||
- [x] Define error codes in `src/error-codes.ts`.
|
||||
- [x] Write Zod schemas for all policy types in `src/types/schemas.ts` + `src/types/schemas.md`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Policy implementations (`src/policies/`)
|
||||
|
||||
### 3a. Rate limit policy
|
||||
|
||||
- [x] Implement `evaluateRateLimitPolicy(policy, request, store, clock)`:
|
||||
- Sliding-window counter keyed by `policy.keyBy` field from request.
|
||||
- Increment counter; if count > `maxRequests`, return deny with `throttleMs` until window resets.
|
||||
- Use injectable `Clock` for window bounds.
|
||||
- [x] Write `rate-limit.test.ts` + `rate-limit.md`.
|
||||
|
||||
### 3b. Access control policy
|
||||
|
||||
- [x] Implement `evaluateAccessControlPolicy(policy, request)`:
|
||||
- Check `denylist` first (explicit deny overrides).
|
||||
- Check `allowlist` next (explicit allow).
|
||||
- If `requireAuth` and no `peerId`/`userId`, deny.
|
||||
- [x] Write `access-control.test.ts` + `access-control.md`.
|
||||
|
||||
### 3c. Quota policy
|
||||
|
||||
- [x] Implement `evaluateQuotaPolicy(policy, request, store, clock)`:
|
||||
- Track active connection count per key; deny if `maxConnections` exceeded.
|
||||
- Reset on `resetIntervalMs` using injectable `Clock`.
|
||||
- [x] Write `quota.test.ts` + `quota.md`.
|
||||
|
||||
### 3d. Capability policy
|
||||
|
||||
- [x] Implement `evaluateCapabilityPolicy(policy, request)`:
|
||||
- Check if request `capabilities` includes ALL `policy.requiredCapabilities`.
|
||||
- If `policy.target` is set, only apply when `request.target` matches the pattern (exact or glob).
|
||||
- [x] Write `capability.test.ts` + `capability.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Governance engine (`src/core/engine/`)
|
||||
|
||||
- [x] Implement `createGovernanceEngine(options?)`:
|
||||
- Stores policies in a sorted array (descending by `priority`).
|
||||
- Maintains audit log as bounded ring buffer (`auditLogSize`, default 1000).
|
||||
- [x] Implement `engine.addPolicy(policy)` — validates with Zod; inserts in priority order.
|
||||
- [x] Implement `engine.removePolicy(id)`.
|
||||
- [x] Implement `engine.evaluate(request, signal?)`:
|
||||
- Check `signal.aborted`.
|
||||
- Iterate policies in priority order; evaluate via the appropriate evaluator.
|
||||
- First `deny` → short-circuit (deny wins over allow at same priority).
|
||||
- Collect `appliedPolicies`.
|
||||
- Write to audit log.
|
||||
- Emit event-bus event per decision type.
|
||||
- OTel: span `governance.evaluate`, counter `governance.decisions_total` (attribute: `allowed`).
|
||||
- [x] Implement `engine.getAuditLog(limit?)`.
|
||||
- [x] Write `engine.test.ts` (≤500 lines): allow, deny, throttle, multi-policy, priority ordering, audit log.
|
||||
- [x] Write `engine.md` (≥200 words).
|
||||
|
||||
---
|
||||
|
||||
## 5. Property-based tests (`tests/property.test.ts`)
|
||||
|
||||
- [x] Deny always wins when a deny policy matches, regardless of allow policies.
|
||||
- [x] Rate-limit counter is monotonically non-decreasing per key within a window.
|
||||
|
||||
---
|
||||
|
||||
## 6. Docs and verify
|
||||
|
||||
- [x] All per-file `.md` files per layout in `proposal.md`.
|
||||
- [x] `universal/network/network-governance/README.md`.
|
||||
- [x] `bunx turbo lint typecheck test test:coverage --filter=@suaveplan/network-governance` — all green, 100%.
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/network-governance`.
|
||||
- [x] Update `CLAUDE.md`. Archive change folder.
|
||||
Reference in New Issue
Block a user