Skip to content
NoPM

Portfolio case study

I build the systems that make unclear requirements impossible.

NoPM is a lite project management tool your agent operates over REST, CLI or MCP. Governance (immutable audit, scoped tokens, four-eyes approval) is built into the data layer. Built solo in eight waves with AI agents.

Sign in →

The thesis

Governance belongs in the data layer.

Every mutation calls writeAudit() once, fail-closed. Postgres triggers keep the log append-only, so even a service key cannot rewrite history. The UI uses the same API your agent does.

“One surface for humans and agents: 121 documented endpoints, OpenAPI, llms.txt, scoped tokens. No bypass.”

The wedge

Three things most PM tools can't claim together.

agent-native
Every action is a REST endpoint: scoped resource:action tokens, OpenAPI, an llms.txt index, a nopm CLI and MCP. Any agent drives it the same way the console does.
governed
Every mutation writes one append-only audit row. Deletes need four-eyes: a different checker approves, enforced by a Postgres CHECK. Even a service key can't rewrite history.
governed triangle
Timeline, resource and budget changes pass one audit and sign-off gate. NoPM records who changed what and who approved it. It is not a scheduler, time tracker or ERP.

What was built

Governance spine, minimal registers.

The governed record beside Jira, Slack and Teams.

governance is invisible to the operator — provable in audit
$ nopm tasks rm task_8f3a \
    --reason "superseded by revised cutover plan"
⏸ delete requested — pending a checker's
  approval (four-eyes)

$ nopm approve req_2c91
✓ approved & committed
  # a different person's token: task:delete:approve
  # one immutable audit row written

$ nopm audit --limit 1
actor_label      action       target
checker (agent)  task.delete  task_8f3a
  • Governed Kanban (drag = audited) and Gantt with rebaseline gate
  • Trust Center: immutable audit log, provenance, tokens
  • Change control, notifications, deck export (PDF / PPTX / HTML)
  • Link invites: role-capped, revocable, audited
  • Workspace, Project, Milestone, Task (maker-checker delete)
  • Risk and Issue registers with scoring
  • Clashes: schedule, resource and dependency conflicts
  • Resources, allocation, cost rate, capacity
  • Budget: planned / committed / actual / EVM (CPI, SPI, EAC, VAC)
  • Sprints: burndown and velocity
  • BYOK assistant (your own API key, AES-256-GCM at rest, ephemeral scoped token). NoPM ships no model.
  • Versioned documents and charters

Proof metrics

Proof metrics.

170traced requirements (R-IDs) in RTM
7547governance-focused tests (vitest)
121documented REST endpoints + OpenAPI

These counts are generated from the RTM, the test run and the API route tree. CI fails if they drift. Tests ask first: can a maker self-approve?

Invariants (stated, not counted)

  • One immutable audit row per mutation.
  • No bundled LLM. No model key ships.

Reviewed, then closed

Ten defects found, then closed.

A 2026-09-06 review confirmed ten defects. Two sprints closed or narrowed them the same day.

All ten defects
  • Finding (morning)
    Eleven mutating routes wrote no audit row, including BYOK key set and delete.
    State (evening)
    Closed. A coverage test fails the build on any unaudited write. The key lifecycle is audited without the key.
  • Finding (morning)
    Ordinary writes persist before the audit row; only two flows are transactional.
    State (evening)
    Open by decision. The Trust Center says so.
  • Finding (morning)
    One scope both raised and decided change requests. A token requester made the DB check vacuous.
    State (evening)
    Closed. change:decide is a separate scope agents never hold. Requester and decider are typed identities, checked in the API and the database.
  • Finding (morning)
    Unlocking a frozen baseline needed no reason.
    State (evening)
    Partly closed. A reason is required in the API, dialog and CLI. The same person can still unlock.
  • Finding (morning)
    Audit rows had no client origin or request id.
    State (evening)
    Partly closed. Every row records the declared client and a request id, including rows written inside database functions. No hash chain yet.
  • Finding (morning)
    No record could point at a Jira key or Slack thread.
    State (evening)
    Closed. Tasks, projects, risks, issues and decisions carry source, external_ref and external_url.
  • Finding (morning)
    No public page said NoPM complements Jira, Slack or Teams.
    State (evening)
    Closed. README, landing, this page and the product doc say it.
  • Finding (morning)
    Lock and rebaseline had no navigation home.
    State (evening)
    Closed. Baselines lives under Governance. The demo page moved to My Work.
  • Finding (morning)
    The audit immutability triggers had no regression test.
    State (evening)
    Closed. A migration-text test fails if any migration drops, disables or bypasses them.
  • Finding (morning)
    Tool and metric counts drifted between surfaces.
    State (evening)
    Closed. Counts are generated and CI-checked.

Review, sprint receipts and re-run are in the repo under docs/research and sprint-log.

Stack & method

Production stack, built with AI agents.

Technology

  • Next.js 16 App Router, TypeScript, Tailwind v4
  • Supabase Postgres, RLS, Auth, Storage
  • Vercel deploy, Cloudflare DNS
  • vitest 7547 tests, governance first
  • jsPDF / pptxgenjs board-pack exports

Discipline

  • Requirement traceability matrix (RTM), checked in CI
  • Architecture decision records (ADRs)
  • Sprint log per wave
  • AI multi-agent workflow, solo build

See the governance live.

Every endpoint is documented. Every mutation is audited.

Sign in →OpenAPI specAgent API (llms.txt)