Cory Policht
← Back to the Devlog
DEVLOG · 2026-08-17 .. 2026-08-23 · BUILD

The hub keeps going: interface requests, Gitea ingestion, and pgvector.

Project Hub lands ADR-008 interface requests with transactional accept-to-spawn semantics, a Gitea issues connector with deep links, pgvector embedding migrations, and strict deploy-drift guards.

WINDOW
Start2026-08-17
End2026-08-23
Kindbuild
Projectsproject-hub
Delivered
  • project-hub
    ADR-008 interface requests and contract registry
    Adds contracts and interface requests (migrations 012/013), introducing a shared HubKey prefix grammar, contract CRUD, and a provider request ledger with explicit created_at tracking for aging queries.
    project-hub-api · #36project-hub-ui · #21project-hub-ui · #22
  • project-hub
    Atomic disposition state machine with accept-to-spawn guarantee
    Implements the five-state machine for interface requests in Core logic. Accepting a request atomically spawns a provider backlog work item inside a single database transaction protected by a foreign key constraint.
    project-hub-api · #37
  • project-hub
    Gitea issues connector and deep-linkable work items
    Ingests Gitea issues into canonical work items with derived ownership, exposing canonical keys like /items/:key across both API and UI.
    project-hub-api · #32project-hub-api · #35project-hub-ui · #18
  • project-hub
    pgvector embeddings migration and llm-proxy adapter
    Adds vector storage migrations to Postgres, pins the integration test fixtures to pgvector containers, and routes embedding requests through llm-proxy.
    project-hub-api · #41
  • project-hub
    Deploy-drift guards and undefined-CSS-token gate
    Adds CI validation to catch undefined design tokens and automated post-merge smoke tests against deployed frontend bundles.
    project-hub-ui · #17

Project Hub started as a read-only viewer for portfolio state. In this window, it turned into an active coordination layer. When multiple independent services and agent swarms evolve simultaneously, inter-service contracts cannot just be informal slack messages or unindexed markdown notes. They need formal lifecycle management, explicit cross-project dependency tracking, and atomic execution guarantees.

The centerpiece of this burst is ADR-008: Interface Requests (#36). Rather than letting consuming services guess when another project will deliver a needed API change, consumer projects can now file formal interface requests against a published contract. The backing storage was split cleanly across two migrations (012_contracts.sql and 013_interface_requests.sql), sharing a unified prefix key allocator (HubKey) with standard work items. Importantly, created_at was tracked independently of updated_at: once requests transition through triage and deferral states, measuring how long a request has sat unanswered becomes impossible if you only have a mutation timestamp.

The state machine governing these requests (#37) models all five legal edges and rigidly enforces twenty illegal transitions in Core domain logic. When a provider team accepts a breaking interface request, the acceptance must atomically spawn the provider’s backing implementation task. We enforced this transactionally in Postgres: the work item is inserted first, and the interface request updates its disposition with a foreign key to spawned_work_item_id in the same unit of work. An “accepted request pointing at nothing” is physically impossible at the schema level.

To surface this work cleanly, the project-hub-ui board added a provider ledger view with aging buckets (#21, #22), alongside deep linking support for canonical work item keys at /items/:key (#18). On the backend, we also landed the Gitea issues connector (#35) to map repository issues into the canonical workspace with derived ownership, and integrated a pgvector embedding store (#41) backed by llm-proxy to enable semantic search across portfolio tasks.

Lessons learned

  • Make illegal lifecycle states physically unstorable in the schema. Relying on application-layer orchestration to ensure an accepted contract request creates a work item will eventually fail under network timeouts or concurrent races. A foreign key constraint requiring spawned_work_item_id on accepted records turns an orchestration invariant into an unbreachable database rule.
  • Never conflate creation timestamps with mutation timestamps when tracking aging. As soon as an entity moves through review, comment, or triage stages, updated_at moves with it. If aging reports (“requests waiting over 30 days”) rely on updated timestamps, triaging a stuck item paradoxically resets its urgency clock.
  • Abstract contract test suites keep in-memory twins from drifting. When supporting an in-memory test double alongside real Postgres storage, write the verification suite once as an abstract base class. Running the exact same 100+ assertions against both ensures the in-memory double reflects real transactional semantics without hidden drift.
  • Lint design tokens in CI before shipping cross-package frontend bundles. Consuming design systems across separate npm repositories often fails silently when a referenced CSS variable name shifts. Adding an explicit undefined-token lint step in the pipeline prevents unstyled cards and broken theme tokens from ever hitting production.