Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ build
tmp
node_modules
.env
.env.bak
.env.bak.*
.env.tmp.*

coverage

Expand Down
6 changes: 5 additions & 1 deletion apps/dev-playground/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ test-results/
playwright-report/

# Auto-generated types (endpoint-specific, varies per developer)
shared/appkit-types/serving.d.ts
shared/appkit-types/serving.d.ts

# Database plugin playground artifacts generated by `appkit db introspect`
config/database/schema.ts
config/database/migrations/
59 changes: 59 additions & 0 deletions apps/dev-playground/config/database/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Database Plugin Playground Fixture

This fixture exercises the database plugin against a real Lakebase project.

## Quick start

For both new and existing databases:

```bash
pnpm exec tsx ../../packages/shared/src/cli/index.ts db init
```

`db init` will:

1. Ask which Databricks profile to use (or use `--profile`).
2. Ask which Lakebase project (or use `--project`).
3. Create or reuse your per-user dev branch (`dev-{slug}-{hash}`).
4. Write `.env` (`PGHOST`, `PGDATABASE`, `LAKEBASE_ENDPOINT`, etc.).
5. Detect whether the target schema is empty or populated.
6. Run the right path: `setup:dev` for a new database, `introspect + verify` for an existing one.

For scripted use (no prompts):

```bash
pnpm exec tsx ../../packages/shared/src/cli/index.ts db init \
--profile DEFAULT \
--project projects/ditadi-taskflow \
--from introspect \
--schema public \
--yes
```

`--from` accepts:

- `migrate` — schema.ts is the source of truth. Generates a migration from
`config/database/schema.ts` and applies it to the dev branch.
- `introspect` — the live branch is the source of truth. Writes
`config/database/schema.ts` from the branch's existing tables.

Without `--from`, `db init` probes the target schema and suggests one (empty
schema → `migrate`, populated schema → `introspect`).

## Lower-level commands

These are what `db init` composes. Use them directly only when `init` is not
the right shape:

```bash
appkit db introspect --schema public # write schema.ts from live DB
appkit db setup:dev --name init --seed # generate + migrate + seed + verify
appkit db migration generate --name <change> # diff schema.ts → migration SQL
appkit db migrate up # apply pending migrations (CI/prod)
appkit db verify # drift check (CI-friendly)
appkit db seed # run seed.sql against current DB
```

In CI and production, prefer `migration generate`, `migrate up`, and `verify`
over `init`/`setup:dev`. Those are the explicit primitives that don't refuse
production.
166 changes: 166 additions & 0 deletions apps/dev-playground/config/database/seed.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
-- AML demo fixture for the AppKit database plugin.
-- Data only. Table structure is created from config/database/schema.ts via
-- `appkit db migration generate` and `appkit db migrate up`.

INSERT INTO cases (
case_id,
entity_id,
entity_name,
risk_score,
risk_level,
status,
case_type,
typologies,
alert_ids,
alert_count,
cluster_id,
assigned_to,
is_historical
) VALUES
(
'CASE-001',
'ENT-1001',
'Acme Trading LLC',
87,
'High',
'In Review',
'Structuring',
'smurfing,cash_deposits',
'ALT-001,ALT-002',
2,
'CL-01',
'Jane Doe',
FALSE
),
(
'CASE-002',
'ENT-1002',
'Globex Imports',
73,
'Medium',
'New',
'Sanctions Screening',
'sanctions_proximity',
'ALT-003',
1,
'CL-02',
'John Smith',
FALSE
)
ON CONFLICT (case_id) DO UPDATE SET
entity_id = EXCLUDED.entity_id,
entity_name = EXCLUDED.entity_name,
risk_score = EXCLUDED.risk_score,
risk_level = EXCLUDED.risk_level,
status = EXCLUDED.status,
case_type = EXCLUDED.case_type,
typologies = EXCLUDED.typologies,
alert_ids = EXCLUDED.alert_ids,
alert_count = EXCLUDED.alert_count,
cluster_id = EXCLUDED.cluster_id,
assigned_to = EXCLUDED.assigned_to,
is_historical = EXCLUDED.is_historical,
updated_at = NOW();

INSERT INTO activity_log (log_id, case_id, action, actor, details, metadata)
VALUES
(
'LOG-001',
'CASE-001',
'case_opened',
'system',
'High-risk structuring case generated from AML model output.',
'{"source":"aml_gold.cases","severity":"HIGH"}'::jsonb
),
(
'LOG-002',
'CASE-001',
'assigned',
'supervisor',
'Assigned to Jane for enhanced due diligence.',
'{"queue":"EDD"}'::jsonb
)
ON CONFLICT (log_id) DO UPDATE SET
action = EXCLUDED.action,
actor = EXCLUDED.actor,
details = EXCLUDED.details,
metadata = EXCLUDED.metadata;

INSERT INTO investigation_notes (note_id, case_id, author, content, note_type)
VALUES
(
'NOTE-001',
'CASE-001',
'Jane Doe',
'Initial review confirms unusual cash deposit velocity across linked accounts.',
'analyst_note'
),
(
'NOTE-002',
'CASE-002',
'John Smith',
'Screening match requires business ownership validation before escalation.',
'triage_note'
)
ON CONFLICT (note_id) DO UPDATE SET
author = EXCLUDED.author,
content = EXCLUDED.content,
note_type = EXCLUDED.note_type;

INSERT INTO ai_summaries (
case_id,
summary,
trigger_reason,
suspicious_patterns,
typology_tags,
recommended_actions,
linked_accounts_count,
previous_alerts_count,
model,
raw_json
) VALUES
(
'CASE-001',
'Customer activity shows repeated cash deposits below reporting thresholds.',
'High composite risk score and linked alerts.',
'["structured_cash_deposits","rapid_movement"]'::jsonb,
'["structuring","layering"]'::jsonb,
'["request_source_of_funds","review_linked_entities"]'::jsonb,
4,
2,
'aml-briefing-agent-fixture',
'{"confidence":"high"}'::jsonb
)
ON CONFLICT (case_id) DO UPDATE SET
summary = EXCLUDED.summary,
trigger_reason = EXCLUDED.trigger_reason,
suspicious_patterns = EXCLUDED.suspicious_patterns,
typology_tags = EXCLUDED.typology_tags,
recommended_actions = EXCLUDED.recommended_actions,
linked_accounts_count = EXCLUDED.linked_accounts_count,
previous_alerts_count = EXCLUDED.previous_alerts_count,
model = EXCLUDED.model,
raw_json = EXCLUDED.raw_json;

INSERT INTO alert_triage (alert_id, decision, reviewer, reason, case_id)
VALUES
(
'ALT-001',
'investigate',
'Jane Doe',
'Alert is consistent with structuring typology.',
'CASE-001'
),
(
'ALT-003',
'review',
'John Smith',
'Potential sanctions proximity requires second-level review.',
'CASE-002'
)
ON CONFLICT (alert_id) DO UPDATE SET
decision = EXCLUDED.decision,
reviewer = EXCLUDED.reviewer,
reason = EXCLUDED.reason,
case_id = EXCLUDED.case_id,
decided_at = NOW();
1 change: 1 addition & 0 deletions packages/shared/src/cli/commands/db/__tests__/db.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe("dbCommand", () => {
test("registers database subcommands", () => {
expect(dbCommand.name()).toBe("db");
expect(dbCommand.commands.map((command) => command.name())).toEqual([
"init",
"introspect",
"migration",
"migrate",
Expand Down
Loading