ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
{
"name": "@app/config",
"version": "0.1.0",
"private": true,
"description": "This app's configuration: the deployment contract per process, the app brand and the feature flags.",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"typecheck": "tsc -p tsconfig.json --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"@conusai-kit/brand": "catalog:kit",
"@conusai-kit/core": "catalog:kit",
"@conusai-kit/platform": "catalog:kit",
"@conusai-sdk/node-sdk": "catalog:",
"@conusai-sdk/observe-node": "catalog:",
"pg": "catalog:",
"zod": "catalog:",
"@conusai-kit/host": "catalog:kit",
"@conusai-kit/business": "catalog:kit"
},
"devDependencies": {
"@types/node": "26.6.5",
"@types/pg": "catalog:",
"typescript": "5.9.3"
}
}
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Who may come into this app and who may found an organization in it (ADR-062), read by
// `@conusai-kit/host/postgres/config`.
export {
ADMISSION_TOKEN_VAR,
IDENTITY_LINK_VAR,
identityLinkOf,
ORGANIZATION_SELF_SERVICE_VAR,
organizationSelfServiceOf,
SINGLE_ORGANIZATION_VAR,
singleOrganizationOf,
} from "@conusai-kit/host/postgres/config"
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The agent module's one deployment setting: how many days a finished run (its step log and its
// conversation) is kept before the hourly sweep deletes it (modules/agent, the run retention job).
//
// AGENT_RUN_RETENTION_DAYS a whole number of days from 1 to 3650; 90 when unset or blank.
//
// A value out of that range is refused with the variable's name, never its value.
export const DEFAULT_AGENT_RUN_RETENTION_DAYS = 90
const MIN_DAYS = 1
const MAX_DAYS = 3650
export function agentRunRetentionDaysOf(env: Record<string, string | undefined> = process.env): number {
const raw = env.AGENT_RUN_RETENTION_DAYS?.trim()
if (!raw) return DEFAULT_AGENT_RUN_RETENTION_DAYS
const days = /^\d+$/.test(raw) ? Number(raw) : Number.NaN
if (!Number.isInteger(days) || days < MIN_DAYS || days > MAX_DAYS) {
throw new Error(`AGENT_RUN_RETENTION_DAYS must be a whole number of days from ${MIN_DAYS} to ${MAX_DAYS}`)
}
return days
}
+16
View File
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The AI settings of this app, read by `@conusai-kit/host/postgres/config`: which model the platform's
// AI gateway is asked for, and which backend serves the knowledge module.
export {
AI_MODEL_VAR,
aiModelOf,
DEFAULT_AI_MODEL,
EMBEDDING_MODEL_VAR,
embeddingModelOf,
KNOWLEDGE_BACKEND_VAR,
KNOWLEDGE_BACKENDS,
type KnowledgeBackendChoice,
knowledgeBackendOf,
knowledgeBackendSchema,
} from "@conusai-kit/host/postgres/config"
+217
View File
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The app brand (decision AT-D11, spec §10.2). This file is the one place a customer replaces
// ConusAI's name, logo and colours with their own: the neutral, status and type tokens stay with
// the brand package, so a replacement cannot break contrast in either theme.
//
// `defineBrand` is the whole API. It checks what it is given -- a name, hex colours, logo paths on
// this app's own origin, tokens that cannot break out of a style attribute -- and derives the
// colours' runtime theme roles through `themeStyle()` of @conusai-kit/brand (`--primary`, its text,
// `--ring` held at text contrast per theme, `--sidebar-primary`, `--chart-1`, and the accent tint; brand-kit
// plan §3.5). The
// layout writes that layer on `<html>` on the server, so there is no flash of another brand.
//
// A per-organization brand (an org's own colours, logos and name) is a runtime layer over this one,
// rendered by the organization layout from `organizations.branding` (modules/branding).
import { type BrandReport, isHexColor, themeStyle, tokens } from "@conusai-kit/brand"
export interface AppBrand {
/** The product name, used in the document title, the shell and e-mail subjects. */
readonly name: string
/** The short name for narrow places (a tab, a badge). */
readonly shortName: string
/** The fill colour of buttons and focus rings, `#rgb` or `#rrggbb`. Absent keeps the ConusAI palette. */
readonly primary?: string
/** A secondary tint for highlights, `#rgb` or `#rrggbb`. */
readonly accent?: string
/** A path under the app's own origin. No external host serves a logo (self-hosted D20). */
readonly logo: { readonly light: string; readonly dark: string; readonly favicon: string }
/** Overrides of non-colour brand tokens (`--brand-radius-*`, fonts) beyond what `primary` and `accent` derive. An empty object adds none. */
readonly tokens: Readonly<Record<string, string>>
/** "Built on ConusAI" in the footer. Off by default: the customer sells their own product. */
readonly attribution: boolean
/** The default locale when a user and their organization have none (kit-i18n negotiates the rest). */
readonly defaultLocale: "en" | "lt"
/** The theme a person gets until they choose one. Absent: dark, the brand's own; `system` follows the device. */
readonly defaultTheme?: "dark" | "light" | "system"
/** The first page and the sign-in. Without it they show the app's name on the brand's scenery. */
readonly signIn?: SignInBrand
}
/** One text in each language of the app. */
export type Localized = Readonly<Record<AppBrand["defaultLocale"], string>>
export interface SignInBrand {
/** A path on this origin, in apps/web/public: the picture behind the way in. */
readonly picture?: string
/** The name on the way in, when it differs from `name`. */
readonly wordmark?: string
/** Draw `logo` beside the name there. On by default. */
readonly logo?: boolean
/** One strong line. */
readonly tagline?: Localized
/** Up to three: `icon` is a name of `NAV_ICONS` of @conusai-kit/core. */
readonly highlights?: readonly { readonly icon?: string; readonly text: Localized }[]
}
export interface BrandInput {
readonly name: string
readonly shortName?: string
readonly primary?: string
readonly accent?: string
readonly logo?: AppBrand["logo"]
readonly tokens?: Readonly<Record<string, string>>
readonly attribution?: boolean
readonly defaultLocale?: AppBrand["defaultLocale"]
readonly defaultTheme?: AppBrand["defaultTheme"]
readonly signIn?: SignInBrand
}
const TOKEN_NAME = /^(?:--brand-)?[a-z][a-z0-9-]*$/
/** What a token value may be: colours, lengths and `light-dark()`/`color-mix()` of them. Not `;`, braces, quotes, `url(` or `\`. */
const TOKEN_VALUE = /^[#a-zA-Z0-9(),.%\s/-]+$/
const FORBIDDEN_IN_VALUE = /url\s*\(|expression\s*\(|@import|javascript:/i
// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are what is refused.
const CONTROL = /[\u0000-\u001f\u007f]/
/** A path on this origin: one leading slash, no scheme and no second slash that would make it a host. */
const isOwnPath = (value: string): boolean => /^\/(?!\/)[^\s\\]*$/.test(value) && !CONTROL.test(value)
/** Names the field and never the value: the value may be a customer's. */
const refuse = (field: string, why: string): never => {
throw new Error(`defineBrand: ${field} ${why}`)
}
const printable = (field: string, value: string): string => {
if (value.trim().length === 0 || value.length > 60 || CONTROL.test(value))
refuse(field, "is 1 to 60 printable characters")
return value
}
export function defineBrand(input: BrandInput): AppBrand {
const name = printable("name", input.name)
const shortName = printable("shortName", input.shortName ?? input.name)
for (const field of ["primary", "accent"] as const) {
const value = input[field]
if (value !== undefined && !isHexColor(value)) refuse(field, "is a hex colour such as #1a1a8c")
}
const logo = input.logo ?? { light: "/favicon.png", dark: "/favicon.png", favicon: "/favicon.png" }
for (const [variant, path] of Object.entries(logo)) {
if (!isOwnPath(path)) refuse(`logo.${variant}`, "is a path on this app's own origin, starting with one slash")
}
const overrides = input.tokens ?? {}
for (const [tokenName, value] of Object.entries(overrides)) {
if (!TOKEN_NAME.test(tokenName)) refuse("token name", "is a lower-case custom property name such as radius-panel")
if (!TOKEN_VALUE.test(value) || FORBIDDEN_IN_VALUE.test(value)) {
refuse(`token ${tokenName}`, "may hold colours and lengths only: no url(), no ; and no braces")
}
}
const signIn = input.signIn
if (signIn?.picture !== undefined && !isOwnPath(signIn.picture)) {
refuse("signIn.picture", "is a path on this app's own origin, starting with one slash")
}
if (signIn?.wordmark !== undefined) printable("signIn.wordmark", signIn.wordmark)
if ((signIn?.highlights?.length ?? 0) > 3) refuse("signIn.highlights", "holds at most three")
return Object.freeze({
name,
shortName,
...(input.primary === undefined ? {} : { primary: input.primary }),
...(input.accent === undefined ? {} : { accent: input.accent }),
logo: Object.freeze({ ...logo }),
tokens: Object.freeze({ ...overrides }),
attribution: input.attribution ?? false,
defaultLocale: input.defaultLocale ?? "en",
...(input.defaultTheme === undefined ? {} : { defaultTheme: input.defaultTheme }),
...(signIn === undefined ? {} : { signIn: Object.freeze({ ...signIn }) }),
})
}
/** The way in as a page draws it, in one language: plain values, so it is loader data. */
export interface AuthBrand {
readonly name: string
readonly logo?: { readonly light: string; readonly dark: string }
readonly picture?: string
readonly tagline?: string
readonly highlights: readonly { readonly icon?: string; readonly text: string }[]
}
export function authBrand(brand: AppBrand, locale: string): AuthBrand {
const language = locale === "lt" ? "lt" : "en"
const { signIn } = brand
return {
name: signIn?.wordmark ?? brand.name,
...(signIn?.logo === false ? {} : { logo: { light: brand.logo.light, dark: brand.logo.dark } }),
...(signIn?.picture ? { picture: signIn.picture } : {}),
...(signIn?.tagline ? { tagline: signIn.tagline[language] } : {}),
highlights: (signIn?.highlights ?? []).map(({ icon, text }) => ({
...(icon ? { icon } : {}),
text: text[language],
})),
}
}
/**
* The public brand document the identity service reads (`GET /api/brand/identity`): what its hosted sign-in
* pages draw, and `theme`, the organization's roles when the platform has any. Nothing here is a secret.
*/
export function identityBrandDocument(
brand: AppBrand,
theme?: { readonly light: object; readonly dark: object },
organization: { readonly name?: string; readonly logo?: string } = {},
) {
const { signIn } = brand
const perLanguage = <T>(read: (language: "en" | "lt") => T) => ({ en: read("en"), lt: read("lt") })
const logo = organization.logo ? { light: organization.logo, dark: organization.logo } : brand.logo
return {
version: 1,
name: signIn?.wordmark ?? organization.name ?? brand.name,
...(signIn?.logo === false ? {} : { logo: { light: logo.light, dark: logo.dark } }),
favicon: brand.logo.favicon,
...(signIn?.picture ? { picture: signIn.picture } : {}),
...(signIn?.tagline ? { tagline: signIn.tagline } : {}),
highlights: perLanguage((language) =>
(signIn?.highlights ?? []).map(({ icon, text }) => ({ ...(icon ? { icon } : {}), text: text[language] })),
),
defaultLocale: brand.defaultLocale,
tokens: brandTokenStyle(brand),
...(theme ? { theme: { light: theme.light, dark: theme.dark } } : {}),
}
}
export const appBrand: AppBrand = defineBrand({
name: "Example App",
shortName: "Example",
// Paths on this app's origin. `/favicon.png` is the ConusAI mark, which apps/web/vite.config.ts emits from
// @conusai-kit/brand; your own logos go in apps/web/public when this app becomes yours.
logo: { light: "/favicon.png", dark: "/favicon.png", favicon: "/favicon.png" },
// primary: "#1a1a8c", // your fill colour; the focus ring (and link colour) is derived per theme
// accent: "#f4c430",
attribution: false,
defaultLocale: "en",
})
/**
* This app's brand as custom properties, for the `style` attribute of one element: what `primary`
* and `accent` derive, then the explicit tokens (which win). A name may be written `--brand-primary`
* or just `primary`.
*/
export function brandTokenStyle(brand: AppBrand = appBrand): Record<string, string> {
const derived = themeStyle({
...(brand.primary === undefined ? {} : { primary: brand.primary }),
...(brand.accent === undefined ? {} : { accent: brand.accent }),
}).style as Record<string, string>
return {
...derived,
...Object.fromEntries(
Object.entries(brand.tokens).map(([name, value]) => [name.startsWith("--") ? name : `--brand-${name}`, value]),
),
}
}
/** What the colours of this brand measure in each theme, for a settings page or a test. */
export function brandContrast(brand: AppBrand = appBrand): BrandReport {
return themeStyle(brand.primary === undefined ? {} : { primary: brand.primary }).report
}
/** Proof that the brand package is the only source of colour: the app ships no literal of its own. */
export const BRAND_TOKEN_NAMES: readonly string[] = Object.keys(tokens)
+99
View File
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Service configuration is evaluated only after authorized dispatch; transport selects its authority.
import { gatewayBusinessServiceHooks } from "@conusai-kit/business/server"
import { type CallerOptions, type Container, type ModuleManifest, serviceRefusal } from "@conusai-kit/core"
import { createNativeServiceReadiness, type Profile } from "@conusai-kit/platform"
import type { AppEnv } from "./env.js"
type Hooks = Pick<CallerOptions, "prepareServices" | "serviceReadiness">
export function businessServiceComposition(
env: Pick<AppEnv, "deployment"> & Partial<Pick<AppEnv, "platform" | "production">>,
modules: readonly ModuleManifest[],
profile: Profile,
options: {
model?: string
supportChannel?: string | ((container: Container) => string | undefined)
guidesIdentityNamespace?: string | ((container: Container) => string | undefined)
} = {},
): Hooks {
if (!modules.some((module) => module.serviceRequirements?.length)) return {}
let current: Hooks | undefined
const configured = (): Hooks => {
if (profile === "convex") throw serviceRefusal("profile_unsupported")
try {
current ??= gatewayBusinessServiceHooks(env.deployment, profile)
return current
} catch {
throw serviceRefusal("configuration_missing")
}
}
return {
prepareServices: async (container, context, requirements) => {
if (
!requirements.some(
(requirement) =>
requirement.transport === "business" &&
requirement.partitionRequired &&
Object.hasOwn(requirement.operations, context.procedure),
)
)
return
await configured().prepareServices?.(container, context, requirements)
},
serviceReadiness: {
profile,
observe: async (requirement, context) => {
if (requirement.transport === "platform") {
// Resolve from the actual dispatch container each time; do not retain a selector
// across a port replacement or accept a selector from procedure/browser input.
const supportChannel =
requirement.service !== "support"
? undefined
: typeof options.supportChannel === "function"
? context.container
? options.supportChannel(context.container)
: undefined
: options.supportChannel
const guidesIdentityNamespace =
requirement.service !== "guides"
? undefined
: typeof options.guidesIdentityNamespace === "function"
? context.container
? options.guidesIdentityNamespace(context.container)
: undefined
: options.guidesIdentityNamespace
const native = createNativeServiceReadiness(env.deployment, {
profile,
...(options.model === undefined ? {} : { model: options.model }),
...(supportChannel === undefined ? {} : { supportChannel }),
// Offline code guides use the fixed local namespace, but still pass the simulated
// native protocol. Production never derives a selector from this fallback.
...(guidesIdentityNamespace === undefined
? env.platform === "fake" && env.production === false && requirement.service === "guides"
? { guidesIdentityNamespace: "app" }
: {}
: { guidesIdentityNamespace }),
})
// The kernel has already resolved and authorized this project's membership.
// Support is an org service; observe that same project's containing org,
// while keeping the original project scope for the procedure and repository.
const admissionContext =
requirement.service === "support" &&
context.module === "support" &&
context.procedure === "support.projectProcessingStatus.get" &&
context.scope.level === "project"
? {
...context,
scope: { level: "org" as const, orgId: context.scope.orgId, actor: context.scope.actor },
}
: context
return native.observe(requirement, admissionContext)
}
if (requirement.transport !== "business") throw serviceRefusal("observation_missing")
const observer = configured().serviceReadiness
if (!observer) throw serviceRefusal("observation_missing")
return observer.observe(requirement, context)
},
},
}
}
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The one setting of the documents module's extraction that a deployment chooses: which kinds of page
// the model is never asked about. The kinds themselves (`text`, `table`, `drawing`, `scan`, `empty`) are
// the module's vocabulary and are checked where they are used (`pageClassesOf` of modules/documents);
// this only reads the variable, so the configuration package depends on no module.
export const DOCUMENTS_EXCLUDED_PAGE_CLASSES_VAR = "DOCUMENTS_EXCLUDED_PAGE_CLASSES"
/**
* The page classes named by `DOCUMENTS_EXCLUDED_PAGE_CLASSES`, a comma-separated list (`drawing,empty`);
* undefined when it is not set, so the module's own default applies. An empty value is a list of
* nothing: every page is read.
*/
export function excludedPageClassNamesOf(
env: Record<string, string | undefined> = process.env,
): readonly string[] | undefined {
const raw = env[DOCUMENTS_EXCLUDED_PAGE_CLASSES_VAR]
if (raw === undefined) return undefined
return raw
.split(",")
.map((name) => name.trim())
.filter((name) => name !== "")
}
+118
View File
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The deployment contract of this app, per process (app-template spec §9.1, decision AT-D20).
//
// @conusai-kit/host reads the platform's variables and the Postgres profile's three roles; what is
// here is the profile this app was generated on, and the one-login and Convex profiles, which stay
// the template's own. A process that is handed a URL it may not hold does not start.
import {
type DeploymentContract,
loadDeploymentContract,
loadPostgresAccess,
MIGRATE_URL_VAR,
type PostgresAccess,
refusedVariable,
requiredVariable,
type Service,
} from "@conusai-kit/host/postgres/config"
import { DeploymentEnvError, type Profile } from "@conusai-kit/platform"
export {
FAKE_PLATFORM_VAR,
isProduction,
MIGRATE_URL_VAR,
PLATFORM_VARS,
PlatformEnvError,
type PlatformMode,
type PostgresAccess,
publicUrlOf,
READER_URL_VAR,
RUNTIME_URL_VAR,
roleOf,
SERVICES,
type Service,
} from "@conusai-kit/host/postgres/config"
/** The persistence profile this app was generated on (spec §5.1). `kit template:prune` removes the others. */
export const PROFILE: Profile = "postgres"
/** The one login of a MariaDB or MongoDB profile: no RLS to bind, so every process holds the same URL (AT-D20 is Postgres-only). */
export interface SingleLoginAccess {
readonly url: string
}
export const MARIADB_URL_VAR = "MARIADB_URL"
export const MONGODB_URL_VAR = "MONGODB_URL"
export const REDIS_URL_VAR = "REDIS_URL"
export interface AppEnv extends DeploymentContract {
readonly postgres?: PostgresAccess
/** The mariadb profile's one login (`MARIADB_URL`). */
readonly mariadb?: SingleLoginAccess
/** The mongodb profile's one login (`MONGODB_URL`, a replica set: AT-D15). */
readonly mongodb?: SingleLoginAccess
/** The BullMQ queue's Valkey (`REDIS_URL`) of the mariadb and mongodb profiles; absent in the migrate job. */
readonly redis?: SingleLoginAccess
readonly convex?: { readonly url: string; readonly adminKey: string }
}
/**
* Parses the environment of one process. Fails at boot, naming the variable and what to do, so a
* misconfigured deployment never reaches its first request (the Showcase pattern, spec §9.1).
*/
export function loadAppEnv(
service: Service,
env: Record<string, string | undefined> = process.env,
profile: Profile = PROFILE,
): AppEnv {
const contract = loadDeploymentContract(service, env, profile)
if (profile === "convex") {
if (service !== "web") {
throw new DeploymentEnvError(`the ${service} process is not supported by the Convex app runtime`)
}
if (env[MIGRATE_URL_VAR]?.trim()) {
refusedVariable(MIGRATE_URL_VAR, service, "Convex deployments do not use a Postgres owner URL")
}
return {
...contract,
convex: {
url: requiredVariable(env, "CONVEX_SELF_HOSTED_URL", "link a Convex service to this project"),
adminKey: requiredVariable(
env,
"CONVEX_SELF_HOSTED_ADMIN_KEY",
"the server-side key of the linked Convex service",
),
},
}
}
if (profile === "mariadb" || profile === "mongodb") {
// One login and no owner: the three-role split exists to make row-level security bind and
// neither engine has it. The Postgres variables are not read on this profile.
const engine = profile === "mariadb" ? "MariaDB" : "MongoDB"
const variable = profile === "mariadb" ? MARIADB_URL_VAR : MONGODB_URL_VAR
const store = { url: requiredVariable(env, variable, `link a ${engine} service to this project`) }
// The migrate job writes the schema and enqueues nothing; web and worker share the BullMQ queue.
const redis =
service === "migrate"
? undefined
: {
url: requiredVariable(
env,
REDIS_URL_VAR,
"link a Redis (Valkey) service to this project: this profile's job queue is BullMQ",
),
}
return {
...contract,
...(profile === "mariadb" ? { mariadb: store } : { mongodb: store }),
...(redis ? { redis } : {}),
}
}
if (profile !== "postgres") {
throw new DeploymentEnvError(`the ${profile} profile is not supported by the app runtime yet`)
}
return { ...contract, postgres: loadPostgresAccess(service, env) }
}
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Feature flags with their defaults (spec §4.1). A module declares its own flags in its manifest;
// these are the app-wide ones. A flag is read through the registry, never from this object
// directly, so a module's flag and an app's flag are read the same way.
export const appFlags = {
/** The in-app audit viewer for organization administrators (spec §9.4). */
"app.audit-viewer": { default: true },
/** Sign-in without an identity provider. Refused at boot in production by @conusai-kit/auth. */
"app.dev-login": { default: false },
} as const satisfies Record<string, { default: boolean }>
export type AppFlag = keyof typeof appFlags
+100
View File
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// What the apps and the modules of this repository read their configuration from.
export {
ADMISSION_TOKEN_VAR,
IDENTITY_LINK_VAR,
identityLinkOf,
ORGANIZATION_SELF_SERVICE_VAR,
organizationSelfServiceOf,
SINGLE_ORGANIZATION_VAR,
singleOrganizationOf,
} from "./access.js"
export { agentRunRetentionDaysOf, DEFAULT_AGENT_RUN_RETENTION_DAYS } from "./agent.js"
export type { KnowledgeBackendChoice } from "./ai.js"
export {
AI_MODEL_VAR,
aiModelOf,
DEFAULT_AI_MODEL,
EMBEDDING_MODEL_VAR,
embeddingModelOf,
KNOWLEDGE_BACKEND_VAR,
KNOWLEDGE_BACKENDS,
knowledgeBackendOf,
} from "./ai.js"
export {
type AppBrand,
type AuthBrand,
appBrand,
authBrand,
BRAND_TOKEN_NAMES,
type BrandInput,
brandContrast,
brandTokenStyle,
defineBrand,
identityBrandDocument,
} from "./brand.js"
export { businessServiceComposition } from "./business-services.js"
export { DOCUMENTS_EXCLUDED_PAGE_CLASSES_VAR, excludedPageClassNamesOf } from "./documents.js"
export {
type AppEnv,
FAKE_PLATFORM_VAR,
isProduction,
loadAppEnv,
MARIADB_URL_VAR,
MIGRATE_URL_VAR,
MONGODB_URL_VAR,
PLATFORM_VARS,
PlatformEnvError,
type PlatformMode,
type PostgresAccess,
PROFILE,
publicUrlOf,
READER_URL_VAR,
REDIS_URL_VAR,
RUNTIME_URL_VAR,
roleOf,
SERVICES,
type Service,
type SingleLoginAccess,
} from "./env.js"
export { type AppFlag, appFlags } from "./flags.js"
export {
appMigrationFiles,
appRoot,
DIALECT_JOBS_MIGRATION,
DIALECT_LIVE_MIGRATION,
type DialectProfile,
dialectMigrationFiles,
dialectModuleMigrationFiles,
expectedDialectMigrations,
expectedMigrations,
GRANT_MIGRATIONS,
liveCaptureMigrations,
type MigrationFile,
missingMigrations,
moduleMigrationFiles,
moduleMigrationFolder,
} from "./migrations.js"
export {
createRedactor,
currentObservability,
type ErrorTrackingClient,
FILTERED,
type LogAttributes,
type Logger,
type LogLevel,
type ObservabilityConfig,
type Otlp,
observabilityConfig,
observabilityLine,
quietObservability,
type Redactor,
type ServerObservability,
type StartOptions,
secretsOf,
startObservability,
} from "./observability.js"
export { auditEventsOf, DEV_USAGE_ORG, type PlatformClients, platformClients, probePlatform } from "./platform.js"
export { assertBoundByRowLevelSecurity, assertOwnsSchema, type RoleFacts, roleFacts, roleLine } from "./postgres.js"
export { ProfileCompositionError, profileRegistry } from "./profile.js"
+103
View File
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Which migrations this build of the app expects to have run. The Postgres profile's list is the one
// `@conusai-kit/host` applies and compares the database against; the dialect profiles' lists are here.
import { existsSync } from "node:fs"
import { readdir } from "node:fs/promises"
import { join } from "node:path"
import type { Registry } from "@conusai-kit/core"
import {
appRoot,
expectedMigrationNames,
liveCaptures,
type MigrationFile,
moduleMigrationFolder,
} from "@conusai-kit/host/postgres/config"
export {
appMigrationFiles,
appRoot,
GRANT_MIGRATIONS,
type MigrationFile,
moduleMigrationFiles,
moduleMigrationFolder,
} from "@conusai-kit/host/postgres/config"
/** Every migration name a current Postgres schema has recorded, in the order the migrate job applies them. */
export const expectedMigrations = expectedMigrationNames
/** The capture trigger of each live collection, as a migration name and its table, on any profile. */
export const liveCaptureMigrations = liveCaptures
/** The mariadb profile's live change log, built by the migrate job (`liveMysqlSchemaStatements`). */
export const DIALECT_LIVE_MIGRATION = "0008_kit-live"
/** What a database that has `applied` still lacks of `expected`. Newer-than-this-build is not behind. */
export const missingMigrations = (expected: readonly string[], applied: ReadonlySet<string>): string[] =>
expected.filter((name) => !applied.has(name))
/** The profiles that keep their own dialect files (spec §5.3): SQL on MariaDB, JSON commands on MongoDB. */
export type DialectProfile = "mariadb" | "mongodb"
const DIALECT_EXTENSION: Record<DialectProfile, string> = { mariadb: ".sql", mongodb: ".json" }
const DIALECT_FILE: Record<DialectProfile, RegExp> = {
mariadb: /^\d{4}_[a-z0-9-]+\.sql$/,
mongodb: /^\d{4}_[a-z0-9-]+\.json$/,
}
/**
* The kernel's files of a dialect profile: `migrations/<profile>` in an app pruned to it (`kit
* template:prune --profile` moves them there), `profiles/<profile>/migrations` before that.
*/
export async function dialectMigrationFiles(
profile: DialectProfile,
root: string = appRoot(),
): Promise<MigrationFile[]> {
const pruned = join(root, "migrations", profile)
const dir = existsSync(pruned) ? pruned : join(root, "profiles", profile, "migrations")
const extension = DIALECT_EXTENSION[profile]
return (await readdir(dir))
.filter((name) => DIALECT_FILE[profile].test(name))
.sort()
.map((name) => ({ name: name.slice(0, -extension.length), path: join(dir, name) }))
}
/** Each module's files of a dialect profile, from the folder its manifest declares for it. */
export async function dialectModuleMigrationFiles(
registry: Registry,
profile: DialectProfile,
root: string = appRoot(),
): Promise<MigrationFile[]> {
const extension = DIALECT_EXTENSION[profile]
const files: MigrationFile[] = []
for (const { module, path } of registry.migrations[profile]) {
const dir = moduleMigrationFolder({ module, path }, root)
const names = (await readdir(dir)).filter((name) => name.endsWith(extension)).sort()
for (const name of names) {
files.push({ name: `${module}/${name.slice(0, -extension.length)}`, path: join(dir, name) })
}
}
return files
}
/**
* The migration the migrate job of a dialect profile builds at run time from the job adapter's own
* entity (`kit_processed_events`, the outbox relay's exactly-once ledger), as the Postgres profile's
* 0001_kernel.sql carries it.
*/
export const DIALECT_JOBS_MIGRATION = "0007_kit-jobs"
/** What a current schema of a dialect profile has recorded: the kernel's files, the jobs table, then the modules'. No grants: one login. */
export async function expectedDialectMigrations(
registry: Registry,
profile: DialectProfile,
root: string = appRoot(),
): Promise<string[]> {
const kernel = (await dialectMigrationFiles(profile, root)).map((file) => file.name)
const modules = (await dialectModuleMigrationFiles(registry, profile, root)).map((file) => file.name)
// The live collections (TS6.2): MariaDB carries a change log and row triggers per collection;
// MongoDB's change stream needs only the pre-images of each collection, for its deletes.
const captures = liveCaptureMigrations(registry, profile).map(({ name }) => name)
const live = profile === "mariadb" ? [DIALECT_LIVE_MIGRATION, ...captures] : captures
return [...kernel, DIALECT_JOBS_MIGRATION, ...live, ...modules]
}
+24
View File
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Observability for the server processes (spec §9.5), from `@conusai-kit/host/postgres/config`: errors,
// traces and logs, configured from the deployment contract and from nothing else, with secrets and
// tokens taken out before anything leaves the process.
export {
createRedactor,
currentObservability,
type ErrorTrackingClient,
FILTERED,
type LogAttributes,
type Logger,
type LogLevel,
type ObservabilityConfig,
type Otlp,
observabilityConfig,
observabilityLine,
quietObservability,
type Redactor,
type ServerObservability,
type StartOptions,
secretsOf,
startObservability,
} from "@conusai-kit/host/postgres/config"
+11
View File
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The platform's services as this app uses them (spec §9), each built once from the deployment contract:
// the same function for the web host and the worker, from `@conusai-kit/host/postgres/config`.
export {
auditEventsOf,
DEV_USAGE_ORG,
type PlatformClients,
platformClients,
probePlatform,
} from "@conusai-kit/host/postgres/config"
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Which role a process is actually connected as (decision AT-D20): `@conusai-kit/host/postgres/config`.
export {
assertBoundByRowLevelSecurity,
assertOwnsSchema,
type RoleFacts,
roleFacts,
roleLine,
} from "@conusai-kit/host/postgres/config"
+28
View File
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// A registry must keep the custody hooks of every adopted module. Refuse an
// unsupported persistence profile instead of silently filtering those hooks.
import { buildRegistry, type ModuleManifest, type Profile, type Registry } from "@conusai-kit/core"
/** A known deployment composition refusal, not an unexpected registry/runtime failure. */
export class ProfileCompositionError extends Error {
override readonly name = "ProfileCompositionError"
constructor(profile: Profile, unsupported: readonly string[]) {
super(
`The ${profile} profile does not support registered modules: ${unsupported.join(", ")}. ` +
"Keep their original profile, schema and data-rights hooks; profile changes require verified custody settlement.",
)
}
}
export function profileRegistry(modules: readonly ModuleManifest[], profile: Profile): Registry {
const registry = buildRegistry([...modules])
const unsupported = registry.modules
.filter((module) => !module.profiles.includes(profile))
.map((module) => module.id)
.sort()
if (unsupported.length > 0) {
throw new ProfileCompositionError(profile, unsupported)
}
return registry
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"noEmit": false,
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"module": "nodenext",
"moduleResolution": "nodenext",
"types": ["node"]
},
"include": ["src/**/*.ts"],
"//": "The emit build resolves a workspace package through node_modules to its dist, not through the source paths of tsconfig.paths.json: pnpm builds the packages in dependency order, so the dist is there."
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "../tsconfig.paths.json",
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"types": ["node"],
"noEmit": true
},
"include": ["src/**/*.ts"]
}