feat: implement project bus MVP

This commit is contained in:
Codex Lead Engineer
2026-07-30 02:13:26 +02:00
commit bc55924198
28 changed files with 3518 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
# Architecture
## Boundary
The Project Bus coordinates formal project events. It does not execute source
code, clone repositories, inspect artifacts, send chat messages, implement a
project methodology, or decide what a project should build. Those activities
remain with authenticated actors and external systems.
## Components
1. `ProjectBusRequestHandler` implements a small stateless MCP Streamable HTTP
transport over JSON-RPC 2.0.
2. `AuthProvider` turns transport credentials into an immutable principal.
3. `MCPApplication` publishes tools and maps calls to domain operations.
4. `ProjectBusService` enforces role, lifecycle, independence, idempotency,
and gate invariants.
5. `Database` owns migration and transaction boundaries.
6. SQLite relational projections support current-state queries; `event_log`
is the immutable ordered synchronization source.
## Write path
A mutating call is authenticated before tool dispatch. The service opens
`BEGIN IMMEDIATE`, resolves the principal to an active project membership,
checks the idempotency tuple `(project, actor, key)`, validates current state,
updates the relevant projection, appends exactly one event, stores the
canonical response, and commits.
The event and projection update therefore succeed or fail together.
Idempotency replays return the original response without adding an event.
## Event ordering and cursors
SQLite allocates a monotonic integer `cursor` for each event. `sync_since`
filters by project, returns ascending cursors, and returns the last delivered
cursor. Consumers persist that value only after processing the response. The
cursor is global to the database, so gaps within a project's stream are
normal. Consumers must not infer missing project events from gaps.
This is pull synchronization. The MCP server does not wake a ChatGPT thread or
push into a dormant CLI session.
## State model
Work packages move through:
`OPEN → CLAIMED → RESULT_SUBMITTED → IN_REVIEW → REVIEWED → ACCEPTED|REJECTED`
`HOLD` leaves a reviewed package in `REVIEWED`. A new review can be requested
after a completed review. This MVP treats corrections after
`CHANGES_REQUESTED` as a new work package or a new explicitly governed
iteration; it does not silently overwrite submitted result evidence.
## Trust boundaries
- Authentication proves an external subject; project membership grants a role.
- The database is trusted to enforce integrity and append-only triggers.
- Artifact URIs and commit hashes are references, not trusted content.
- Reverse proxy identity headers are not consumed by the built-in adapter.
+36
View File
@@ -0,0 +1,36 @@
# Production Profile
The MVP is suitable for local integration and controlled proof-of-concept use.
Internet-facing or business-critical deployment requires a separate production
hardening work package.
## Required changes
1. Implement the persistence interface for PostgreSQL 16+.
Use transactions, `SELECT … FOR UPDATE` or conditional updates, unique
idempotency constraints, and database roles that cannot update/delete the
event table.
2. Replace `StaticTokenAuthProvider` with validated OIDC/JWT or mTLS workload
identity. Bind issuer, audience, subject, expiry, and revocation policy.
3. Put the service behind TLS and authenticated ingress. Do not trust arbitrary
forwarded identity headers.
4. Run multiple stateless instances only after PostgreSQL conformance and
concurrency tests pass.
5. Add structured audit export, metrics, tracing correlation, alerts, backup,
point-in-time recovery, and restore exercises.
6. Add membership lifecycle, token revocation, retention/export policy, and
project deletion/archival decisions. Event deletion is intentionally absent.
7. Pin and scan the base image; generate an SBOM and sign release artifacts.
8. Run load, fault, client interoperability, and penetration tests.
## MCP activation
The deployed endpoint must support `POST /mcp` with `application/json`. The
current service returns normal JSON instead of SSE and uses no session IDs.
Validate that each intended client accepts this stateless profile before
production gate acceptance.
Chat threads are not push subscribers. A client, automation, or PA invocation
must call `sync_since(last_cursor)`. The cursor itself belongs in durable client
state and is advanced only after successful processing.
+83
View File
@@ -0,0 +1,83 @@
# Threat Model
## Assets
- project instructions, decisions, results, reviews, and escalations;
- actor-role assignments and identity bindings;
- event ordering and audit integrity;
- bearer credentials;
- commit, artifact, and baseline references;
- service/database availability.
## Adversaries
- an unauthenticated network client;
- an authenticated actor exceeding their formal mandate;
- an implementer attempting self-review or self-acceptance;
- a compromised bearer token;
- a malicious artifact URI or oversized request;
- an operator or database account attempting audit-history mutation;
- concurrent clients exploiting check/write races.
## Implemented controls
| Threat | MVP control |
|---|---|
| Identity spoofing in tool args | no actor argument; identity comes from transport |
| Token timing comparison | SHA-256 digest and constant-time comparison |
| Unauthorized project access | active membership lookup per call |
| PA decision overreach | explicit decision/gate/baseline denial |
| Product decision overreach | PO-only product decisions |
| Self-review | reviewer role plus actor independence checks |
| Self-acceptance | PM/PO role and result-submitter identity check |
| Premature gate acceptance | independent APPROVE review required |
| Double claim / TOCTOU | `BEGIN IMMEDIATE` and conditional state update |
| Duplicate delivery | scoped request hash and idempotency record |
| Audit update/delete | SQLite triggers reject both operations |
| Cross-project reads | project filter plus membership resolution |
| Browser cross-origin request | exact configurable Origin allowlist |
| Oversized request | one MiB body limit |
| MIME confusion/caching | strict JSON input, nosniff, no-store |
| SQL injection | parameterized SQL; one controlled placeholder expansion |
Artifact URIs and review evidence are recorded as untrusted data and are never
fetched or executed by the bus.
## Residual risks and production requirements
1. Static bearer credentials have no built-in expiry, rotation endpoint,
audience, issuer, or proof-of-possession. Replace the adapter with validated
OIDC/JWT or mTLS identities.
2. Plain HTTP is available. Use TLS and prohibit direct public access to the
application port.
3. SQLite append-only triggers do not protect against an operator replacing
the entire database file or disabling triggers. Use restricted DB roles,
PostgreSQL permissions, PITR, backups, and an external immutable audit
export for production assurance.
4. The event payload is not cryptographically chained or signed. Add hash
chaining/signatures only if the agreed threat model requires tamper
evidence against privileged database operators.
5. No application rate limiter exists. Apply per-identity limits and request
quotas at the trusted edge.
6. The server emits access metadata but no security audit sink or metrics.
Integrate structured logs, alerts, and privacy-aware retention.
7. Membership deactivation and credential revocation are not exposed as MVP
tools. Operators must treat compromised tokens as an immediate
configuration/restart incident.
8. Role assignment is one role per actor/project. Shared human/agent accounts
undermine independence even when IDs differ and are prohibited operationally.
9. Evidence and artifact digests are caller assertions; the bus does not
verify repository ownership or retrieve content.
10. Dependency-free MCP handling reduces supply-chain surface but has not yet
been interoperability-tested against every intended client version.
## Security verification before activation
- run all tests and an independent code review;
- test the exact Claude Code and ChatGPT MCP clients;
- replace all example tokens and restrict origin/host exposure;
- confirm backup/restore and audit export;
- review log contents for sensitive project data;
- add abuse limits and operational monitoring;
- decide whether privileged-operator tamper evidence is required.
+79
View File
@@ -0,0 +1,79 @@
# MCP Tool Contract
Protocol version: `2025-03-26`
Endpoint: `POST /mcp`
Transport profile: stateless Streamable HTTP with JSON responses
`initialize`, `ping`, and `tools/list` do not require credentials. Every
`tools/call` requires `Authorization: Bearer …`. The server does not implement
server-to-client SSE notifications in this MVP; `GET /mcp` returns `405`.
## Common rules
- Every project-scoped call resolves the authenticated principal to one active
membership.
- Every mutation requires a stable `idempotency_key`.
- Reusing a key with the same operation and canonical arguments replays the
original response. Reusing it with different input returns `conflict`.
- IDs use letters, numbers, `.`, `_`, `:`, `/`, or `-`, up to 128 characters.
- Tool domain failures are MCP tool results with `isError: true`.
- JSON-RPC syntax/method failures use JSON-RPC error objects.
- Missing or invalid credentials return HTTP `401`.
## Tools
| Tool | Allowed role / invariant | Primary event |
|---|---|---|
| `create_project` | SYSTEM only | `project.created` |
| `register_actor` | SYSTEM, PO, PM; PM cannot appoint PO | `actor.registered` |
| `issue_work_package` | PO, PM | `work_package.issued` |
| `claim_work_package` | Architect, Lead Engineer, Implementer; atomic OPEN claim | `work_package.claimed` |
| `submit_result` | claiming actor only | `work_package.result_submitted` |
| `request_review` | PO, PM, PA, implementation/design roles; reviewer independent | `review.requested` |
| `submit_review` | assigned independent review actor | `review.submitted` |
| `record_decision` | product: PO; other kinds: PO/PM; never PA | `decision.recorded` |
| `raise_escalation` | every active member | `escalation.raised` |
| `resolve_escalation` | PO, PM | `escalation.resolved` |
| `link_commit` | every active member; valid hexadecimal object ID | `commit.linked` |
| `link_artifact` | every active member | `artifact.linked` |
| `close_gate` | PO, PM; ACCEPT needs independent approval | `gate.closed` |
| `baseline_release` | PO, PM; included work packages accepted | `baseline.released` |
| `sync_since` | every active member; max 500 events | none |
| `list_pending_actions` | every active member, role-filtered reviews/escalations | none |
| `get_work_package` | every active member | none |
| `get_project` | every active member; includes actor-role roster | none |
The authoritative argument schema is returned by `tools/list`. Clients should
discover it instead of hard-coding optional defaults.
## Event envelope
`sync_since` returns:
```json
{
"cursor": 42,
"event_id": "evt_...",
"project_id": "example",
"event_type": "review.submitted",
"actor_id": "reviewer-1",
"actor_role": "REVIEWER",
"aggregate_type": "review",
"aggregate_id": "rev_...",
"correlation_id": "WP-001",
"causation_event_id": null,
"created_at": "2026-07-30T00:00:00.000Z",
"payload": {}
}
```
Event payloads are version-zero MVP structures. Consumers should ignore
unknown fields and key behavior off `event_type`, not prose.
## MCP interoperability limits
The transport implements the required request/response subset used by MCP
tools over Streamable HTTP. It is stateless, does not issue `Mcp-Session-Id`,
does not accept JSON-RPC batches, and provides no resumable SSE channel.
Interoperability must be checked against the concrete ChatGPT and Claude Code
clients during activation.