Skip to content

feat(hangar): Postgres-first spine with ingest API - #132

Merged
Coldaine merged 1 commit into
mainfrom
feat/postgres-first-hangar
Jul 30, 2026
Merged

feat(hangar): Postgres-first spine with ingest API#132
Coldaine merged 1 commit into
mainfrom
feat/postgres-first-hangar

Conversation

@Coldaine

@Coldaine Coldaine commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Canonical HangarData in Postgres content_snapshots; UI loads via Drizzle
  • POST /api/hangar/ingest (Bearer HANGAR_INGEST_TOKEN) for agent writes
  • hangar.ts is fixture/fallback only; AGENTS/deploy/standup/README updated for coldaine-homelab
  • GHA tags :latest on main for Soil-style cluster pulls

Test plan

  • npm run check
  • After migrate + hangar:seed-spine, Shell DATA lamp shows PG
  • Ingest upsert visible without editing TypeScript

Summary

Introduces a Postgres-first Hangar data flow with authenticated agent ingestion.

Highlights

  • Stores canonical HangarData in the content_snapshots Postgres table.
  • Adds Drizzle-based spine reads and upserts with static fixture fallback.
  • Adds POST /api/hangar/ingest, protected by Bearer HANGAR_INGEST_TOKEN.
  • Validates and upserts supported Hangar entity kinds.
  • Adds spine fallback-status UI handling and ingest tests.
  • Adds hangar:seed-spine for bootstrapping Postgres.
  • Updates deployment, operational, AGENTS, standup, and README documentation.
  • Configures Docker metadata to publish :latest for main builds.

Labels

postgres · hangar · api · deployment · documentation

Canonical HangarData lives in content_snapshots; UI reads via Drizzle;
agents upsert through POST /api/hangar/ingest. hangar.ts is fixture/fallback
only. Deploy docs point at coldaine-homelab; GHA tags :latest on main.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 30, 2026 22:50
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4285eed8-ef1d-4286-a24b-ad180de6540d

📥 Commits

Reviewing files that changed from the base of the PR and between 4aaba0a and bbce91d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • .github/workflows/image.yml
  • AGENTS.md
  • README.md
  • db/hangar/migrations/2026-07-30-content-snapshots.sql
  • db/hangar/seed-spine.ts
  • db/hangar/standup.md
  • docs/deploy.md
  • package.json
  • src/__tests__/hangar-ingest.test.ts
  • src/__tests__/shell.test.tsx
  • src/app/api/hangar/ingest/route.ts
  • src/app/layout.tsx
  • src/components/HangarProvider.tsx
  • src/components/Shell.tsx
  • src/lib/hangar-read-status.ts
  • src/lib/store.tsx
  • src/server/hangar/drizzle.ts
  • src/server/hangar/ingest.ts
  • src/server/hangar/schema.ts
  • src/server/hangar/spine.ts

📝 Walkthrough

Walkthrough

The PR introduces a Postgres-backed Hangar spine stored in content_snapshots, an authenticated entity-ingest API, UI wiring for spine reads and fallback status, a seed command, and updated deployment and workflow documentation.

Changes

Hangar Postgres spine

Layer / File(s) Summary
Spine storage and read/write access
db/hangar/migrations/..., src/server/hangar/schema.ts, src/server/hangar/drizzle.ts, src/server/hangar/spine.ts
Defines the content_snapshots table, Drizzle access, payload validation, Postgres reads with static fallback, and snapshot upserts.
Authenticated entity ingestion
src/server/hangar/ingest.ts, src/app/api/hangar/ingest/route.ts, src/__tests__/hangar-ingest.test.ts
Validates supported entities, authorizes Bearer tokens, upserts records into the spine, and maps failures to HTTP responses.
UI spine initialization and fallback status
src/app/layout.tsx, src/components/HangarProvider.tsx, src/lib/store.tsx, src/lib/hangar-read-status.ts, src/components/Shell.tsx, src/__tests__/shell.test.tsx
Passes spine data and read status through the UI, uses spine-backed state, and displays tested static-spine fallback messaging.
Bootstrap commands and deployment guidance
db/hangar/seed-spine.ts, package.json, .github/workflows/image.yml, README.md, AGENTS.md, db/hangar/standup.md, docs/deploy.md
Adds the seed command and dependencies, enables the latest main-branch image tag, and documents the Postgres-first ingest and deployment workflow.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: size:XXL

Suggested reviewers: copilot

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant IngestRoute
  participant IngestLogic
  participant Postgres
  Agent->>IngestRoute: POST entity record with Bearer token
  IngestRoute->>IngestLogic: authorize and validate request
  IngestLogic->>Postgres: read and upsert hangar snapshot
  Postgres-->>IngestLogic: return persisted snapshot
  IngestLogic-->>IngestRoute: return ingest result
  IngestRoute-->>Agent: return JSON response
Loading

Poem

I’m a bunny with a spine to store,
Hopping facts through Postgres’ door.
Tokens guard each tiny byte,
Static banners shine in flight.
Seed the Hangar, tag it bright—
Hop, review, and ship tonight! 🐇

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/postgres-first-hangar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Coldaine
Coldaine merged commit 827fc37 into main Jul 30, 2026
4 of 5 checks passed
@coderabbitai coderabbitai Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Jul 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a Postgres-first “spine snapshot” for HangarData (stored in content_snapshots) and an authenticated ingest API for writing canonical Hangar facts, while keeping src/data/hangar.ts as an offline fixture/fallback. This aligns the Hangar UI with the “Postgres is canonical” workflow and adds operator tooling/docs for seeding and deployment.

Changes:

  • Add Postgres spine snapshot read/write via Drizzle (getHangarSpine / putHangarSpine) and wire the app layout to hydrate the client store from the snapshot.
  • Add POST /api/hangar/ingest (Bearer token) to upsert entities into the spine snapshot, plus a hangar:seed-spine bootstrap script.
  • Update Shell UX copy/tests, docs, and CI image tagging to reflect the new Postgres-first spine and ingest workflow.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/server/hangar/spine.ts Implements Postgres-first Hangar spine snapshot read/write with static fallback.
src/server/hangar/schema.ts Defines Drizzle table mapping for content_snapshots.
src/server/hangar/ingest.ts Implements ingest parsing and entity upsert into the spine snapshot.
src/server/hangar/drizzle.ts Provides cached Drizzle client over the shared pg pool.
src/lib/store.tsx Allows client store hydration from a provided spine snapshot and read-status.
src/lib/hangar-read-status.ts Adds a new “spine” lane for fallback copy/labels.
src/components/Shell.tsx Updates the banner/copy to reflect “STATIC SPINE” fallback messaging.
src/components/HangarProvider.tsx Threads initialData + initialSpineRead into the client store provider.
src/app/layout.tsx Switches initial hydration from inventory-items to the Postgres spine snapshot.
src/app/api/hangar/ingest/route.ts Adds the authenticated ingest route handler with Zod error reporting.
src/tests/shell.test.tsx Updates Shell fallback tests to assert spine fallback messaging/behavior.
src/tests/hangar-ingest.test.ts Adds tests for ingest body parsing and entity upsert behavior.
README.md Updates repo “how it fits” to the Postgres-first + ingest workflow and new homelab repo.
package.json Adds Drizzle/Zod deps and hangar:seed-spine script.
docs/deploy.md Updates verified deploy facts and documents the ingest + seed flow.
db/hangar/standup.md Updates DB standup doc to reflect Postgres-first spine cutover and ownership.
db/hangar/seed-spine.ts Adds script to seed content_snapshots from the TypeScript fixture.
db/hangar/migrations/2026-07-30-content-snapshots.sql Adds content_snapshots table migration.
AGENTS.md Replaces prior guidance with “Postgres is canonical; ingest API is the write path”.
.github/workflows/image.yml Adds :latest tag publishing on main pushes for cluster pulls.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +104 to +110
const current = await getHangarSpine();
const base =
current.source === 'postgres' && isHangarDataPayload(current.data)
? current.data
: current.data;

const next = applyEntity(base, body.entity, body.record);
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

/** Minimal structural check — full integrity stays in hangar-integrity / ingest Zod. */
Comment on lines +55 to +58
const row = rows[0];
if (!row || !isHangarDataPayload(row.payload)) {
return staticFallback('postgres-error');
}

switch (entity) {
case 'unit':
return { ...data, units: upsertById(data.units, withId as unknown as Unit) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: as unknown as Unit bypasses per-entity type validation

Each case uses as unknown as Unit (and similar casts) without field-level validation. Since parseIngestBody validates record only as z.record(z.string(), z.unknown()), a payload like {"entity":"unit","record":{"id":"u-1"}} passes all checks but writes a structurally broken entry into the spine snapshot.

Consider adding per-entity Zod schemas so the type assertion is grounded in validated data.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread src/lib/store.tsx
const [source, setSource] = useState<SourcePreference>(() => readStoredSource());
const [spotlightId, setSpotlightId] = useState<string | null>(null);
const [units, setUnits] = useState<Unit[]>(() => hangarData.units);
const [units, setUnits] = useState<Unit[]>(() => spine.units);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: units state is captured from mount-time spine and won't update if spine changes after initial render

The useState initializer captures the first render's spine.units reference. If initialData ever changes after mount, the units state stays frozen at the original value.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

export type HangarDrizzle = NodePgDatabase<typeof schema>;

let db: HangarDrizzle | null = null;
let boundPool: unknown = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: boundPool is typed as unknown instead of the return type of getHangarPool()

let boundPool: unknown = null defeats TypeScript's ability to catch type mismatches. If getHangarPool() returns Pool | null, type boundPool as Pool | null to match.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
src/lib/store.tsx 77 readStoredLensMissionId validates against static fixture, not dynamic spine — Postgres-only missions silently lose their stored lens ID

SUGGESTION

File Line Issue
src/server/hangar/ingest.ts 69 as unknown as type casts bypass per-entity field validation
src/lib/store.tsx 294 units state pinned to mount-time spine, won't reflect later initialData changes
src/server/hangar/drizzle.ts 9 boundPool typed as unknown instead of the pool return type
Files Reviewed (20 files)
  • .github/workflows/image.yml
  • AGENTS.md
  • README.md
  • db/hangar/migrations/2026-07-30-content-snapshots.sql
  • db/hangar/seed-spine.ts
  • db/hangar/standup.md
  • docs/deploy.md
  • package.json
  • src/__tests__/hangar-ingest.test.ts
  • src/__tests__/shell.test.tsx
  • src/app/api/hangar/ingest/route.ts
  • src/app/layout.tsx
  • src/components/HangarProvider.tsx
  • src/components/Shell.tsx
  • src/lib/hangar-read-status.ts
  • src/lib/store.tsx
  • src/server/hangar/drizzle.ts
  • src/server/hangar/ingest.ts
  • src/server/hangar/schema.ts
  • src/server/hangar/spine.ts

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4-flash · Input: 70.2K · Output: 17.5K · Cached: 1.7M

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants