Skip to content
Closed
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
1 change: 1 addition & 0 deletions apps/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@corbits/agent-directory": "workspace:*",
"@corbits/approvals": "workspace:*",
"@corbits/artifacts": "github:corbitsdev/corbits-artifacts#81049ed24a64e927498c7238bda6ffa66b63d2ab",
"@corbits/chat": "workspace:*",
"@corbits/commands": "workspace:*",
"@corbits/folded-runs": "workspace:*",
Expand Down
61 changes: 61 additions & 0 deletions apps/hub/src/artifacts-mount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Hub-side artifacts engine mount — the host's own analog of
* `@corbits/dock`'s `mountArtifacts` (see Scout's
* `packages/agent-dock/src/artifacts-store.ts`). `@corbits/artifacts`
* (git pin) persists artifacts + immutable version history in Postgres;
* its `artifact`/`artifact_version` tables carry hard FKs into the
* host's own `public.tenant` / `public.principal` tables, so the engine
* MUST point at the same Postgres cluster as this hub's control plane.
*
* Degrades cleanly when unconfigured: `ARTIFACTS_DATABASE_URL` unset
* (and no explicit `databaseUrl` passed) means "no artifacts
* persistence", logged once at boot, never thrown — same contract as
* the dock mount.
*
* This module lands the mount + factory only. Tenant-scoped HTTP
* list/search/read routes are intentionally not registered here yet —
* Library still reads the asset-shim surface until those routes ship.
*/
import { getLogger } from "@intx/log";
import {
createArtifactDb,
runArtifactMigrations,
type ArtifactDb,
} from "@corbits/artifacts";

const log = getLogger(["hub", "artifacts-mount"]);

export type MountArtifactsOptions = {
/** Defaults to `process.env.ARTIFACTS_DATABASE_URL`. */
databaseUrl?: string;
};

/**
* Handle returned by a successful mount. The `db` is the engine's own
* drizzle handle (the same shape dock's `mountArtifacts` exposes) so a
* later routes module can build the persist/find/search/read surface on
* top of it without re-deriving the connection.
*/
export type ArtifactsMountHandle = {
db: ArtifactDb;
};

export async function mountArtifacts(
options: MountArtifactsOptions = {},
): Promise<ArtifactsMountHandle | undefined> {
const databaseUrl =
options.databaseUrl ?? process.env["ARTIFACTS_DATABASE_URL"];
if (!databaseUrl) {
log.info(
"ARTIFACTS_DATABASE_URL not set — artifacts will not be persisted",
);
return undefined;
}

const { db } = createArtifactDb(databaseUrl);
await runArtifactMigrations(db);
log.info(
"Artifacts engine mounted — artifacts persist as versioned rows by kind",
);
return { db };
}
27 changes: 27 additions & 0 deletions apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { createNeedsYouRoutes } from "@corbits/approvals";
import { createEchoRoutes } from "@workbench/echo";
import { createGitWorkflowPusher } from "@workbench/hub-client";
import { createOnboardingRoutes } from "@workbench/onboarding";
import { mountArtifacts } from "./artifacts-mount";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { type Context, type Next } from "hono";
Expand Down Expand Up @@ -480,6 +481,32 @@ export async function createHub(config: HubConfig) {

app.route("/api/onboarding", createOnboardingRoutes(onboardingDeps));

// Artifacts engine: mounts `@corbits/artifacts` against the same
// Postgres cluster as this hub's control plane (its
// `artifact`/`artifact_version` tables FK into `public.tenant` /
// `public.principal`). Degrades to a no-op when
// `ARTIFACTS_DATABASE_URL` is unset. The handle is available for
// tenant-scoped list/search/read routes; Library still uses the
// asset-shim surface until those land.
//
// The mount runs migrations against the configured DB; if the URL is
// present but points at an unreachable/invalid cluster the migration
// would otherwise throw and take the whole hub down at boot. We catch
// that here so the hub comes up in a degraded (no-artifacts) mode and
// surfaces the failure as a warning rather than a crash.
let artifactsHandle: Awaited<ReturnType<typeof mountArtifacts>>;
try {
artifactsHandle = await mountArtifacts();
} catch (error) {
log.warn(
`Artifacts mount failed — continuing without artifacts persistence: ${error}`,
);
artifactsHandle = undefined;
}
log.info(
`Artifacts handle ${artifactsHandle !== undefined ? "available" : "unavailable (degraded mode)"}`,
);

// Tells the signed-out screen which OAuth buttons to draw, without
// exposing the credentials themselves — just which providers a full
// pair was configured for. No session or tenant is required to ask,
Expand Down
3 changes: 2 additions & 1 deletion apps/hub/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["bun"]
"types": ["bun"],
"customConditions": ["bun"]
},
"include": ["src", "test"]
}
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading