@teamscala/service-boot 0.23.319 → 0.23.321
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +526 -526
- package/src/agreement-gate.d.ts +19 -0
- package/src/agreement-gate.ts +0 -12
- package/src/asset-cache-key.d.ts +11 -0
- package/src/asset-optimize.d.ts +17 -0
- package/src/asset-proxy.d.ts +11 -0
- package/src/asset-social-card.d.ts +17 -0
- package/src/attach-agreement-magic.d.ts +20 -0
- package/src/attach-portal-members.d.ts +23 -0
- package/src/attach-proposal-accept.d.ts +26 -0
- package/src/attach-proposal-magic.d.ts +18 -0
- package/src/attach-transfers-upload.d.ts +27 -0
- package/src/auth-gate.d.ts +28 -0
- package/src/auth-gate.ts +6 -68
- package/src/bind-page-handler.d.ts +18 -0
- package/src/build-client-assets/input-hashing.d.ts +25 -0
- package/src/build-client-assets/source-candidates.d.ts +27 -0
- package/src/build-client-assets.d.ts +50 -0
- package/src/build-client-assets.test.ts +1 -1
- package/src/build-client-assets.ts +1 -1
- package/src/compose-page-title.d.ts +41 -0
- package/src/content-gate/access.d.ts +15 -0
- package/src/content-gate/config-types.d.ts +99 -0
- package/src/content-gate/gate.d.ts +13 -0
- package/src/content-gate/parse-cookie.d.ts +9 -0
- package/src/content-gate/token.d.ts +48 -0
- package/src/content-page-floor.d.ts +39 -0
- package/src/discover-blocks.d.ts +28 -0
- package/src/discover-blocks.ts +1 -1
- package/src/expand-roster.d.ts +34 -0
- package/src/fn-routers/agreement-completion-email.d.ts +14 -0
- package/src/fn-routers/agreement-send-router.d.ts +82 -0
- package/src/fn-routers/ai-health-router.d.ts +35 -0
- package/src/fn-routers/auth-router.d.ts +47 -0
- package/src/fn-routers/cache-admin-router.d.ts +57 -0
- package/src/fn-routers/chat-session-router.d.ts +219 -0
- package/src/fn-routers/email-defaults.d.ts +1 -0
- package/src/fn-routers/email-link-ttl.d.ts +1 -0
- package/src/fn-routers/index.d.ts +18 -0
- package/src/fn-routers/index.ts +0 -2
- package/src/fn-routers/panel-data-router.d.ts +19 -0
- package/src/fn-routers/persist-chat-messages.d.ts +36 -0
- package/src/fn-routers/plans-router.d.ts +25 -0
- package/src/fn-routers/proposal-send-router.d.ts +58 -0
- package/src/fn-routers/public-agreement-router.d.ts +289 -0
- package/src/fn-routers/public-assessment-router.d.ts +68 -0
- package/src/fn-routers/public-gpt-session-router.d.ts +143 -0
- package/src/fn-routers/sign-in-flow-router.d.ts +107 -0
- package/src/fn-routers/task-sync-assign.d.ts +305 -0
- package/src/fn-routers/task-sync-assign.test.ts +1 -2
- package/src/fn-routers/task-sync-assign.ts +4 -4
- package/src/fn-routers/task-sync-bind.test.ts +18 -18
- package/src/fn-routers/task-sync-brief.d.ts +96 -0
- package/src/fn-routers/task-sync-brief.test.ts +17 -20
- package/src/fn-routers/task-sync-brief.ts +14 -15
- package/src/fn-routers/task-sync-dispatch.d.ts +61 -0
- package/src/fn-routers/task-sync-dispatch.test.ts +33 -51
- package/src/fn-routers/task-sync-dispatch.ts +124 -0
- package/src/fn-routers/task-sync-lane.d.ts +49 -0
- package/src/fn-routers/task-sync-lane.ts +125 -0
- package/src/fn-routers/task-sync-projection.d.ts +121 -0
- package/src/fn-routers/task-sync-projection.test.ts +0 -26
- package/src/fn-routers/task-sync-projection.ts +0 -51
- package/src/fn-routers/task-sync-router.d.ts +377 -0
- package/src/fn-routers/task-sync-router.test.ts +2 -14
- package/src/fn-routers/task-sync-router.ts +1075 -0
- package/src/fn-routers/tasks-router.d.ts +25 -0
- package/src/fn-routers/user-preferences-router.d.ts +77 -0
- package/src/fn-routers/work-items-router.d.ts +102 -0
- package/src/gate-ttls.d.ts +6 -0
- package/src/gpt-gate.d.ts +25 -0
- package/src/handlers/bun-stable-watch.d.ts +1 -0
- package/src/handlers/pane-outbox-drain.d.ts +1 -0
- package/src/handlers/projects-digest.d.ts +26 -0
- package/src/handlers/prune-stale-data.d.ts +30 -0
- package/src/handlers/registry-stale-inactive-sweep.d.ts +1 -0
- package/src/handlers/scala-tools-currency-watch.d.ts +1 -0
- package/src/handlers/singleton-tools-currency-watch.d.ts +1 -0
- package/src/handlers/singleton-tools-currency-watch.ts +11 -114
- package/src/handlers/sweep-event-log.d.ts +1 -0
- package/src/handlers/sweep-host-cache.d.ts +1 -0
- package/src/handlers/sweep-host-cache.ts +26 -143
- package/src/handlers/upstream-issue-sync.d.ts +1 -0
- package/src/handlers/whatsapp-liveness-logic.d.ts +34 -0
- package/src/handlers/whatsapp-liveness.d.ts +4 -0
- package/src/hono-error-handler.d.ts +25 -0
- package/src/install-harness.d.ts +45 -0
- package/src/install-logging.d.ts +34 -0
- package/src/install-logging.ts +0 -3
- package/src/load-service-install-row.d.ts +24 -0
- package/src/load-service-prisma-client.d.ts +18 -0
- package/src/mount-orpc-router.d.ts +15 -0
- package/src/mount-orpc-router.ts +1 -1
- package/src/mount-server-packages.d.ts +29 -0
- package/src/mount.d.ts +9 -0
- package/src/mutable-app-ref.d.ts +44 -0
- package/src/page-route-shell-types.d.ts +12 -0
- package/src/page-server-orpc/audit-revert-router.d.ts +37 -0
- package/src/page-server-orpc/hub-mcp-client.d.ts +12 -0
- package/src/page-server-orpc/portal-diagnostics-router.d.ts +49 -0
- package/src/page-server-orpc/puck-pages-router.d.ts +58 -0
- package/src/page-server-orpc/server-client.d.ts +16 -0
- package/src/page-server-orpc/templates-router.d.ts +34 -0
- package/src/page-server-orpc/user-preferences-router.d.ts +79 -0
- package/src/pg-boss-handler-registry.d.ts +27 -0
- package/src/pg-boss-mount.d.ts +42 -0
- package/src/proposal-authorized-emails.d.ts +28 -0
- package/src/proposal-chat-inject.d.ts +17 -0
- package/src/proposal-gate.d.ts +18 -0
- package/src/read-html-template.d.ts +38 -0
- package/src/read-viewport-from-request.d.ts +18 -0
- package/src/readiness-gate.d.ts +16 -0
- package/src/register-api-routes.d.ts +18 -0
- package/src/register-crash-handlers.d.ts +15 -0
- package/src/register-page-routes.d.ts +27 -0
- package/src/register-page-routes.ts +3 -0
- package/src/register-page-server-client.d.ts +11 -0
- package/src/register-registry-page-route/ai-chat-prefetch.d.ts +13 -0
- package/src/register-registry-page-route/graph-cache.d.ts +30 -0
- package/src/register-registry-page-route/meta-title.d.ts +2 -0
- package/src/register-registry-page-route/proposal-approval-email-token.d.ts +14 -0
- package/src/register-registry-page-route/proposal-brand-stamping.d.ts +28 -0
- package/src/register-registry-page-route/resolve-page-data.d.ts +1 -0
- package/src/register-registry-page-route.d.ts +27 -0
- package/src/register-static-assets.d.ts +13 -0
- package/src/register-ws-routes.d.ts +28 -0
- package/src/resolve-app-base-url.d.ts +13 -0
- package/src/resolve-brand-favicon.d.ts +28 -0
- package/src/resolve-brand-favicon.ts +1 -1
- package/src/resolve-from-loader-config.d.ts +32 -0
- package/src/resolve-service-context.d.ts +17 -0
- package/src/resolve-service-port.d.ts +75 -0
- package/src/resolve-theme-mode.d.ts +42 -0
- package/src/resolvers/fetch-org-brand.d.ts +2 -0
- package/src/resolvers/portal-route-paths.d.ts +39 -0
- package/src/resolvers/website-by-host-map.d.ts +12 -0
- package/src/resource-chat-inject.d.ts +46 -0
- package/src/resource-not-found.d.ts +60 -0
- package/src/resource-not-found.ts +0 -10
- package/src/run-boot-hook-list.d.ts +19 -0
- package/src/run-boot-hooks.d.ts +31 -0
- package/src/scala-tools-currency-watchdog.d.ts +10 -0
- package/src/security-headers.d.ts +12 -0
- package/src/serve/serve.d.ts +46 -0
- package/src/serve/stamp-transport-identity.d.ts +32 -0
- package/src/serve/stamp-transport-identity.ts +2 -14
- package/src/server-error-page.d.ts +29 -0
- package/src/start-scheduled-jobs.d.ts +18 -0
- package/src/two-phase-boot.d.ts +136 -0
- package/src/two-phase-boot.ts +17 -85
- package/src/two-phase-health-gate.test.ts +2 -13
- package/src/types.d.ts +46 -0
- package/src/ui-service.d.ts +46 -0
- package/src/ws-boot-self-test.d.ts +19 -0
- package/src/auth-gate.test.ts +0 -70
- package/src/fn-routers/task-sync-close.test.ts +0 -44
- package/src/fn-routers/task-sync-close.ts +0 -82
- package/src/fn-routers/task-sync-hook-lifecycle.test.ts +0 -204
- package/src/fn-routers/task-sync-lane.test.ts +0 -36
- package/src/fn-routers/task-sync-rename.test.ts +0 -53
- package/src/fn-routers/task-sync-rename.ts +0 -111
- package/src/handlers/sweep-host-cache.test.ts +0 -133
- package/src/serve/stamp-transport-identity.test.ts +0 -24
- package/src/two-phase-health-gate-client-only.test.ts +0 -77
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* ORPC procedures for task creation with initial comment.
|
|
7
|
+
* Extracted from @teamscala/orpc/fn-router — primitives tier must not
|
|
8
|
+
* contain application-specific work_items logic.
|
|
9
|
+
*/
|
|
10
|
+
import { type Context } from "./auth-router.ts";
|
|
11
|
+
export declare const tasksRouter: {
|
|
12
|
+
createWithComment: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<Context & Record<never, never>, Context, Context>, import("@orpc/server").MergedCurrentContext<Context, {
|
|
13
|
+
headers: Headers;
|
|
14
|
+
resolveUser?: (headers: Headers, prisma: any) => Promise<import("./auth-router.ts").User | null>;
|
|
15
|
+
prisma: any;
|
|
16
|
+
user: import("./auth-router.ts").User;
|
|
17
|
+
orgId: string | null | undefined;
|
|
18
|
+
}>, import("@orpc/contract").Schema<unknown, unknown>, import("@orpc/contract").Schema<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
data: any;
|
|
21
|
+
}, {
|
|
22
|
+
success: boolean;
|
|
23
|
+
data: any;
|
|
24
|
+
}>, Record<never, never>, Record<never, never>>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* ORPC procedures for a GENERIC per-user preference store backed by the
|
|
7
|
+
* `user_preferences` table. The user is resolved from the ORPC session context
|
|
8
|
+
* (`authed` middleware → `context.user.id`); the client supplies the org + key
|
|
9
|
+
* + value. This is the DB-durable, user-scoped key/value surface any feature
|
|
10
|
+
* can use — the AI browser-control focus (key `ai_ui_focus`) is the first
|
|
11
|
+
* consumer; it is NOT scala-projects-bespoke.
|
|
12
|
+
*
|
|
13
|
+
* Why this exists (over the generated `user_preferences` CRUD): the generated
|
|
14
|
+
* create/update do NOT resolve `user_id` server-side (only `organisation_id`
|
|
15
|
+
* from ctx.orgId), so a client cannot correctly write a per-user row through
|
|
16
|
+
* them. This proc stamps `user_id` from the authenticated caller, making the
|
|
17
|
+
* write safe + per-user. It also emits the `data_change` notify so the
|
|
18
|
+
* realtime surface (regions.watchDataChange) pushes updates to subscribed
|
|
19
|
+
* clients — the cross-region channel for the AI focus (the AiChat panel writes
|
|
20
|
+
* a focus here; the project-list-view in a different region subscribes +
|
|
21
|
+
* reacts, no shared React-context ancestor needed).
|
|
22
|
+
*
|
|
23
|
+
* Ownership is user_id: a caller reads/writes ONLY their own preferences. The
|
|
24
|
+
* org is part of the compound key (per-org isolation) but is client-supplied
|
|
25
|
+
* (the session carries the user, not the active org).
|
|
26
|
+
*
|
|
27
|
+
* DECISION — host-scoping of `user_preferences`: the table is NOT yet a shared
|
|
28
|
+
* `@teamscala/db` model (it lives in scala-projects's schema today), so this proc
|
|
29
|
+
* is genuinely host-scoped: it runs correctly on services whose Prisma client
|
|
30
|
+
* declares `user_preferences` (scala-projects is the first host) + is inert
|
|
31
|
+
* elsewhere. `Context.prisma` is `any`-typed, so the proc COMPILES workspace-
|
|
32
|
+
* wide (no type error on a model a host may not declare) — the `any` is the
|
|
33
|
+
* deliberate seam that lets one generic proc serve hosts incrementally as they
|
|
34
|
+
* adopt the table. Per-user preferences are platform-universal, so the tracked
|
|
35
|
+
* follow-on is to promote `user_preferences` to a shared `@teamscala/db` model
|
|
36
|
+
* (open-migration: `user_preferences→shared-model`, exit: every kind:ui host
|
|
37
|
+
* can mount this proc with no schema precondition) — at which point the proc is
|
|
38
|
+
* universally hostable + the `any` seam can tighten to the shared client. Until
|
|
39
|
+
* then the proc is opt-in per host (declare the table → get the capability).
|
|
40
|
+
*/
|
|
41
|
+
import * as v from "valibot";
|
|
42
|
+
import { type Context } from "./auth-router.ts";
|
|
43
|
+
export declare const userPreferencesRouter: {
|
|
44
|
+
upsert: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<Context & Record<never, never>, Context, Context>, import("@orpc/server").MergedCurrentContext<Context, {
|
|
45
|
+
headers: Headers;
|
|
46
|
+
resolveUser?: (headers: Headers, prisma: any) => Promise<import("./auth-router.ts").User | null>;
|
|
47
|
+
prisma: any;
|
|
48
|
+
user: import("./auth-router.ts").User;
|
|
49
|
+
orgId: string | null | undefined;
|
|
50
|
+
}>, v.ObjectSchema<{
|
|
51
|
+
readonly organisationId: v.StringSchema<undefined>;
|
|
52
|
+
/** The preference key (e.g. `ai_ui_focus`). Namespaced by feature. */
|
|
53
|
+
readonly key: v.StringSchema<undefined>;
|
|
54
|
+
/** The JSON value. Arbitrary per feature — typed by the caller. */
|
|
55
|
+
readonly value: v.AnySchema;
|
|
56
|
+
}, undefined>, import("@orpc/contract").Schema<{
|
|
57
|
+
success: boolean;
|
|
58
|
+
}, {
|
|
59
|
+
success: boolean;
|
|
60
|
+
}>, Record<never, never>, Record<never, never>>;
|
|
61
|
+
get: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<Context & Record<never, never>, Context, Context>, import("@orpc/server").MergedCurrentContext<Context, {
|
|
62
|
+
headers: Headers;
|
|
63
|
+
resolveUser?: (headers: Headers, prisma: any) => Promise<import("./auth-router.ts").User | null>;
|
|
64
|
+
prisma: any;
|
|
65
|
+
user: import("./auth-router.ts").User;
|
|
66
|
+
orgId: string | null | undefined;
|
|
67
|
+
}>, v.ObjectSchema<{
|
|
68
|
+
readonly organisationId: v.StringSchema<undefined>;
|
|
69
|
+
readonly key: v.StringSchema<undefined>;
|
|
70
|
+
}, undefined>, import("@orpc/contract").Schema<{
|
|
71
|
+
value: any;
|
|
72
|
+
updatedAt: any;
|
|
73
|
+
}, {
|
|
74
|
+
value: any;
|
|
75
|
+
updatedAt: any;
|
|
76
|
+
}>, Record<never, never>, Record<never, never>>;
|
|
77
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* ORPC procedure for safely deleting a work_item (initiative/project/task).
|
|
7
|
+
* Refuses the delete when the row still has child rows (parent_id = id), so a
|
|
8
|
+
* delete can NEVER orphan its descendants — the tree must be emptied bottom-up.
|
|
9
|
+
*
|
|
10
|
+
* Initiatives, projects and tasks are all rows of the ONE `work_items` table
|
|
11
|
+
* discriminated by `kind`, and `parent_id` carries no DB-level foreign key, so
|
|
12
|
+
* a plain `work_items.delete` leaves dangling children with no referential
|
|
13
|
+
* integrity to stop it. This one guard covers every level (you may only delete
|
|
14
|
+
* a leaf — a node with no children), which is exactly the "empty it first"
|
|
15
|
+
* behaviour the board wants.
|
|
16
|
+
*/
|
|
17
|
+
import { type Context } from "./auth-router.ts";
|
|
18
|
+
export declare const workItemsRouter: {
|
|
19
|
+
/**
|
|
20
|
+
* Fired client-side right after a task's status is set to
|
|
21
|
+
* "pending_approval" (RaCI: the task's assignee is Responsible; the
|
|
22
|
+
* project's Accountable person is the approver). Walks parent_id up to
|
|
23
|
+
* the nearest project-kind ancestor, emails every accountable person on
|
|
24
|
+
* it. Never throws to the caller — a notification failure must not block
|
|
25
|
+
* the status change that already committed.
|
|
26
|
+
*/
|
|
27
|
+
requestApproval: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<Context & Record<never, never>, Context, Context>, import("@orpc/server").MergedCurrentContext<Context, {
|
|
28
|
+
headers: Headers;
|
|
29
|
+
resolveUser?: (headers: Headers, prisma: any) => Promise<import("./auth-router.ts").User | null>;
|
|
30
|
+
prisma: any;
|
|
31
|
+
user: import("./auth-router.ts").User;
|
|
32
|
+
orgId: string | null | undefined;
|
|
33
|
+
}>, import("@orpc/contract").Schema<unknown, unknown>, import("@orpc/contract").Schema<{
|
|
34
|
+
notified: number;
|
|
35
|
+
}, {
|
|
36
|
+
notified: number;
|
|
37
|
+
}>, Record<never, never>, Record<never, never>>;
|
|
38
|
+
/**
|
|
39
|
+
* Fired client-side right after a comment is created, when the comment
|
|
40
|
+
* text contains an @mention the client already resolved to a team-member
|
|
41
|
+
* id (client does the name matching against the people list it already
|
|
42
|
+
* has loaded; this handler only sends). Never throws to the caller for
|
|
43
|
+
* same reason as requestApproval -- a notification failure must not
|
|
44
|
+
* surface as a failed comment post.
|
|
45
|
+
*/
|
|
46
|
+
notifyMentions: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<Context & Record<never, never>, Context, Context>, import("@orpc/server").MergedCurrentContext<Context, {
|
|
47
|
+
headers: Headers;
|
|
48
|
+
resolveUser?: (headers: Headers, prisma: any) => Promise<import("./auth-router.ts").User | null>;
|
|
49
|
+
prisma: any;
|
|
50
|
+
user: import("./auth-router.ts").User;
|
|
51
|
+
orgId: string | null | undefined;
|
|
52
|
+
}>, import("@orpc/contract").Schema<unknown, unknown>, import("@orpc/contract").Schema<{
|
|
53
|
+
notified: number;
|
|
54
|
+
}, {
|
|
55
|
+
notified: number;
|
|
56
|
+
}>, Record<never, never>, Record<never, never>>;
|
|
57
|
+
/**
|
|
58
|
+
* Org-scoped member list for the task-comments panel — @mention matching AND
|
|
59
|
+
* comment-sender display (name + avatar). ChatPanel.tsx has no
|
|
60
|
+
* serverLoader/orgId prop (client-only overlay content), so it can't do the
|
|
61
|
+
* SSR raw-SQL org-scope join load-project-list-view.ts uses; this exposes
|
|
62
|
+
* the same result via a custom procedure instead of widening service_schema
|
|
63
|
+
* to the raw better_auth_member table.
|
|
64
|
+
*
|
|
65
|
+
* This is the SINGLE source of member display info for the comments panel:
|
|
66
|
+
* it coalesces BOTH name/email/avatar families on the user model into one
|
|
67
|
+
* shape (the `full_name`/`primary_email`/`avatar_url` family AND the
|
|
68
|
+
* better-auth `name`/`email`/`image` family). Returning avatar here lets the
|
|
69
|
+
* client resolve sender name+avatar from this one list and drop the separate
|
|
70
|
+
* `user.findMany` enrichment fetch it previously ran as a second waterfall
|
|
71
|
+
* hop after `comments.findMany` (loading-shape: one wave-parallel hit, not a
|
|
72
|
+
* dependent client-side round-trip chain).
|
|
73
|
+
*/
|
|
74
|
+
orgMembers: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<Context & Record<never, never>, Context, Context>, import("@orpc/server").MergedCurrentContext<Context, {
|
|
75
|
+
headers: Headers;
|
|
76
|
+
resolveUser?: (headers: Headers, prisma: any) => Promise<import("./auth-router.ts").User | null>;
|
|
77
|
+
prisma: any;
|
|
78
|
+
user: import("./auth-router.ts").User;
|
|
79
|
+
orgId: string | null | undefined;
|
|
80
|
+
}>, import("@orpc/contract").Schema<unknown, unknown>, import("@orpc/contract").Schema<{
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
email: string;
|
|
84
|
+
avatar: string | null;
|
|
85
|
+
}[], {
|
|
86
|
+
id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
email: string;
|
|
89
|
+
avatar: string | null;
|
|
90
|
+
}[]>, Record<never, never>, Record<never, never>>;
|
|
91
|
+
deleteEmpty: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<Context & Record<never, never>, Context, Context>, import("@orpc/server").MergedCurrentContext<Context, {
|
|
92
|
+
headers: Headers;
|
|
93
|
+
resolveUser?: (headers: Headers, prisma: any) => Promise<import("./auth-router.ts").User | null>;
|
|
94
|
+
prisma: any;
|
|
95
|
+
user: import("./auth-router.ts").User;
|
|
96
|
+
orgId: string | null | undefined;
|
|
97
|
+
}>, import("@orpc/contract").Schema<unknown, unknown>, import("@orpc/contract").Schema<{
|
|
98
|
+
success: boolean;
|
|
99
|
+
}, {
|
|
100
|
+
success: boolean;
|
|
101
|
+
}>, Record<never, never>, Record<never, never>>;
|
|
102
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Populate the TTL cache from the registry rows. Idempotent; keeps defaults on
|
|
2
|
+
* any miss so the gates ALWAYS render (protective-layers-fail-explicitly). */
|
|
3
|
+
export declare function initGateTtls(): Promise<void>;
|
|
4
|
+
export declare function agTtlMs(): number;
|
|
5
|
+
export declare function propTtlMs(): number;
|
|
6
|
+
export declare function gptTtlMs(): number;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* The PUBLIC GPT identity gate, expressed as config for the content-gate
|
|
7
|
+
* primitive (content-gate/). Unlike proposals/agreements this is NOT access
|
|
8
|
+
* control — every email is a valid visitor (lead capture). Its whole job is to
|
|
9
|
+
* make a returning visitor's identity known AT SSR so the GPT body renders the
|
|
10
|
+
* real conversation on first paint instead of server-rendering the capture gate
|
|
11
|
+
* and then swapping to the chat after hydration (the ssr-then-live-handoff +
|
|
12
|
+
* "know the visitor at SSR first paint" violation: building-applications).
|
|
13
|
+
*
|
|
14
|
+
* Flow: the capture form POSTs {gptId, email} to /api/gpt-access (openCapture →
|
|
15
|
+
* any email matches); the access endpoint sets a signed `gpt_token` cookie. On
|
|
16
|
+
* the next request runContentGate verifies that cookie for this agent and seeds
|
|
17
|
+
* resolved.initialVisitor — which the GPT body serverLoader (ssrGptSession) then
|
|
18
|
+
* uses to SSR-restore the conversation, and GptBodyBlock uses to skip the gate.
|
|
19
|
+
* onUnauthorized is a no-op: an un-gated (first-time) visitor sees the capture
|
|
20
|
+
* form the block renders itself when no visitor is known — that is correct, not
|
|
21
|
+
* a flash (they ARE anonymous), so there is nothing to withhold.
|
|
22
|
+
*/
|
|
23
|
+
import type { AccessConfig, ContentGateConfig } from "./content-gate/config-types.ts";
|
|
24
|
+
export declare const gptGateConfig: ContentGateConfig;
|
|
25
|
+
export declare const gptAccessConfig: AccessConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* The `projects-daily-digest` / `projects-weekly-digest` pg-boss handlers for
|
|
7
|
+
* scala-projects. Same fixed-query shape as prune-stale-data: no row-driven
|
|
8
|
+
* SQL, every query is a literal Prisma call against work_items; the
|
|
9
|
+
* scheduled_job row only carries the cron.
|
|
10
|
+
*
|
|
11
|
+
* Daily digest: per assignee, tasks assigned to them created in the last 24h.
|
|
12
|
+
* Weekly digest: per assignee, their overdue tasks + tasks due in the next 7
|
|
13
|
+
* days. Both group work_items by assignee_id so one org-wide query covers
|
|
14
|
+
* every member, then send one email per assignee via the
|
|
15
|
+
* projects-daily-digest / projects-weekly-digest email_template rows.
|
|
16
|
+
*
|
|
17
|
+
* Registered under `@teamscala/service-boot:projects-daily-digest` and
|
|
18
|
+
* `@teamscala/service-boot:projects-weekly-digest`.
|
|
19
|
+
*/
|
|
20
|
+
import type { ScheduledJobConfig } from "@teamscala/db-validation/registry-schemas/scheduled-job";
|
|
21
|
+
export declare function sendDailyDigest(_ctx: {
|
|
22
|
+
config: ScheduledJobConfig;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export declare function sendWeeklyDigest(_ctx: {
|
|
25
|
+
config: ScheduledJobConfig;
|
|
26
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* The `db-prune-stale-data` pg-boss handler. Reads the `retention` map from the
|
|
7
|
+
* `scheduled_job` row config and prunes stale rows per entry.
|
|
8
|
+
*
|
|
9
|
+
* Writes use Prisma ORM methods (deleteMany/updateMany) via the prisma-registry
|
|
10
|
+
* client — NEVER raw SQL. The `@teamscala/db` client carries the `blockRawSqlWrites`
|
|
11
|
+
* audit extension: `$executeRaw`/`$executeRawUnsafe` are blocked so every write
|
|
12
|
+
* is audited; ORM methods are the canonical audited path.
|
|
13
|
+
*
|
|
14
|
+
* SAFETY — identifier allowlist: each retention key selects a FIXED, hardcoded
|
|
15
|
+
* Prisma model + field (table/field names are literal in code, never derived
|
|
16
|
+
* from the row); the row's value (a duration string) is parsed to a cutoff Date.
|
|
17
|
+
* A row can therefore never inject arbitrary SQL or address an unvetted model.
|
|
18
|
+
* Keys not in the allowlist are skipped + logged.
|
|
19
|
+
*
|
|
20
|
+
* `audit_trail.*` is INTENTIONALLY EXCLUDED: `audit-trail-permanent` (critical)
|
|
21
|
+
* forbids any DELETE FROM audit_trail. The legacy row's
|
|
22
|
+
* `audit_trail.sync_entities: "delete all"` entry is stale drift from before
|
|
23
|
+
* the 2026-06-19 hardening — surfaced (skipped + warned), never executed.
|
|
24
|
+
*
|
|
25
|
+
* Registered under slug `@teamscala/service-boot:prune-stale-data`.
|
|
26
|
+
*/
|
|
27
|
+
import type { ScheduledJobConfig } from "@teamscala/db-validation/registry-schemas/scheduled-job";
|
|
28
|
+
export declare function pruneStaleData({ config, }: {
|
|
29
|
+
config: ScheduledJobConfig;
|
|
30
|
+
}): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -30,40 +30,11 @@ import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
|
30
30
|
import { spawn } from "@teamscala/os/spawn";
|
|
31
31
|
import { pgBossHandlerRegistry } from "../pg-boss-handler-registry.ts";
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* silently taking the npm branch. The previous single-member form
|
|
39
|
-
* (`type: "npm_package"`) made a GitHub-released binary UNREPRESENTABLE, not
|
|
40
|
-
* merely unregistered — which is why codex and zerostack are absent from
|
|
41
|
-
* `config/scala-singleton-tools` entirely. Both are standalone ELF binaries in
|
|
42
|
-
* ~/.local/bin and neither is npm-managed, so neither could be described at all.
|
|
43
|
-
*
|
|
44
|
-
* Their LOCATION is correct and must not be "fixed": `bun-bin-is-unstable`
|
|
45
|
-
* designates ${HOME}/.local/bin as the home for operator CLIs precisely so they
|
|
46
|
-
* never shadow a /usr/local/bin singleton wrapper. The gap was currency
|
|
47
|
-
* tracking, never placement.
|
|
48
|
-
*/
|
|
49
|
-
type VersionSource =
|
|
50
|
-
| {
|
|
51
|
-
type: "npm_package";
|
|
52
|
-
name: string;
|
|
53
|
-
dist_tag: string;
|
|
54
|
-
}
|
|
55
|
-
| {
|
|
56
|
-
/** A GitHub release asset — the shape codex and zerostack ship as. */
|
|
57
|
-
type: "github_release";
|
|
58
|
-
/** "owner/repo". */
|
|
59
|
-
repo: string;
|
|
60
|
-
/**
|
|
61
|
-
* Regex matched against each release asset's name to pick the download.
|
|
62
|
-
* Required: a repo publishing several platform builds has no safe default,
|
|
63
|
-
* and guessing one installs the wrong architecture silently.
|
|
64
|
-
*/
|
|
65
|
-
asset_pattern: string;
|
|
66
|
-
};
|
|
33
|
+
interface VersionSource {
|
|
34
|
+
type: "npm_package";
|
|
35
|
+
name: string;
|
|
36
|
+
dist_tag: string;
|
|
37
|
+
}
|
|
67
38
|
|
|
68
39
|
interface SingletonTool {
|
|
69
40
|
name: string;
|
|
@@ -127,13 +98,9 @@ function semverLt(a: string, b: string): boolean {
|
|
|
127
98
|
/** Read the installed version from a tool's real_path (symlink-resolved binary). */
|
|
128
99
|
async function readInstalledVersion(tool: SingletonTool): Promise<string | null> {
|
|
129
100
|
try {
|
|
130
|
-
|
|
131
|
-
// The package.json path only exists for an npm-sourced JS tool. A
|
|
132
|
-
// github_release tool has no node_modules entry, so it falls through to the
|
|
133
|
-
// `--version` probe below like any other binary.
|
|
134
|
-
if (tool.kind === "js" && tool.install_dir && vs?.type === "npm_package") {
|
|
101
|
+
if (tool.kind === "js" && tool.install_dir) {
|
|
135
102
|
// JS tools: read the installed package's package.json
|
|
136
|
-
const pkgJsonPath = `${tool.install_dir}/node_modules/${
|
|
103
|
+
const pkgJsonPath = `${tool.install_dir}/node_modules/${tool.version_source!.name}/package.json`;
|
|
137
104
|
const pkg = await Bun.file(pkgJsonPath).json() as { version: string };
|
|
138
105
|
return pkg.version;
|
|
139
106
|
}
|
|
@@ -152,30 +119,6 @@ async function readInstalledVersion(tool: SingletonTool): Promise<string | null>
|
|
|
152
119
|
}
|
|
153
120
|
}
|
|
154
121
|
|
|
155
|
-
/**
|
|
156
|
-
* Latest published version for a GitHub-released tool.
|
|
157
|
-
*
|
|
158
|
-
* Reads the repo's latest release tag rather than enumerating assets, because
|
|
159
|
-
* the tag is what carries the VERSION; the asset pattern only decides which
|
|
160
|
-
* build to download at install time. A leading "v" is stripped so the value
|
|
161
|
-
* compares against `readInstalledVersion` output under the same semver parser.
|
|
162
|
-
*/
|
|
163
|
-
async function fetchLatestGithubRelease(repo: string): Promise<string | null> {
|
|
164
|
-
try {
|
|
165
|
-
const res = await fetch(
|
|
166
|
-
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
167
|
-
{ headers: { accept: "application/vnd.github+json" } },
|
|
168
|
-
);
|
|
169
|
-
if (!res.ok) return null;
|
|
170
|
-
const release = (await res.json()) as { tag_name?: string };
|
|
171
|
-
const tag = (release.tag_name ?? "").trim().replace(/^v/, "");
|
|
172
|
-
if (!tag || !parseSemver(tag)) return null;
|
|
173
|
-
return tag;
|
|
174
|
-
} catch {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
122
|
/** Fetch the latest version from npm for a given package + dist-tag. */
|
|
180
123
|
async function fetchLatestVersion(pkg: string, distTag: string): Promise<string | null> {
|
|
181
124
|
try {
|
|
@@ -210,35 +153,7 @@ async function fetchLatestVersion(pkg: string, distTag: string): Promise<string
|
|
|
210
153
|
/** Reinstall a stale tool to the latest version. */
|
|
211
154
|
async function reinstallTool(tool: SingletonTool, latest: string): Promise<boolean> {
|
|
212
155
|
const log = getAppLogger();
|
|
213
|
-
const vs = tool.version_source
|
|
214
|
-
if (!vs) return false;
|
|
215
|
-
|
|
216
|
-
if (vs.type === "github_release") {
|
|
217
|
-
// A GitHub-released binary is NOT installed by a package manager, so there
|
|
218
|
-
// is no "reinstall" verb to call — it would have to be downloaded, verified,
|
|
219
|
-
// made executable and swapped atomically over a file that may be RUNNING.
|
|
220
|
-
// That is a real install path with real failure modes (partial download,
|
|
221
|
-
// wrong architecture, replacing a binary mid-execution), and doing it badly
|
|
222
|
-
// is worse than not doing it: a half-written CLI is a broken CLI.
|
|
223
|
-
//
|
|
224
|
-
// So this reports the drift and stops. Detection is live from today; the
|
|
225
|
-
// automated swap is a separate, deliberate change. Reporting a version the
|
|
226
|
-
// watcher cannot actually install is honest — silently claiming currency
|
|
227
|
-
// would not be.
|
|
228
|
-
log.warn(
|
|
229
|
-
`[singleton-tools-currency] ${tool.name}: STALE, latest ${latest} from ${vs.repo}. Automated github_release install is not implemented — update it manually.`,
|
|
230
|
-
);
|
|
231
|
-
recordEvent({
|
|
232
|
-
kind: "singleton-tools.currency.tick",
|
|
233
|
-
payload: {
|
|
234
|
-
tool: tool.name,
|
|
235
|
-
latest,
|
|
236
|
-
status: "stale-manual-update-required",
|
|
237
|
-
repo: vs.repo,
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
156
|
+
const vs = tool.version_source!;
|
|
242
157
|
|
|
243
158
|
if (tool.kind === "js" && tool.install_dir) {
|
|
244
159
|
// JS tool: bun add in the install_dir
|
|
@@ -297,21 +212,8 @@ async function singletonToolsCurrencyWatch(_ctx: { config: ScheduledJobConfig })
|
|
|
297
212
|
|
|
298
213
|
for (const tool of config.tools) {
|
|
299
214
|
const vs = tool.version_source;
|
|
300
|
-
if (!vs) {
|
|
301
|
-
//
|
|
302
|
-
// This previously `continue`d silently as "not yet inside the mechanism",
|
|
303
|
-
// which is how a gap becomes permanent: nothing distinguishes a tool
|
|
304
|
-
// nobody has declared from one that is up to date, so the watcher reports
|
|
305
|
-
// a clean tick while the tool rots. That is the exact shape of
|
|
306
|
-
// `a-component-may-not-report-a-state-it-has-not-verified` — the run
|
|
307
|
-
// advertises coverage it never performed.
|
|
308
|
-
log.warn(
|
|
309
|
-
`[singleton-tools-currency] ${tool.name}: NO version_source — not tracked, cannot be kept current. Add one to config/scala-singleton-tools.`,
|
|
310
|
-
);
|
|
311
|
-
recordEvent({
|
|
312
|
-
kind: "singleton-tools.currency.tick",
|
|
313
|
-
payload: { tool: tool.name, status: "no-version-source" },
|
|
314
|
-
});
|
|
215
|
+
if (!vs || vs.type !== "npm_package") {
|
|
216
|
+
// No version_source declared — skip (the tool is not yet inside the mechanism)
|
|
315
217
|
continue;
|
|
316
218
|
}
|
|
317
219
|
|
|
@@ -334,12 +236,7 @@ async function singletonToolsCurrencyWatch(_ctx: { config: ScheduledJobConfig })
|
|
|
334
236
|
continue;
|
|
335
237
|
}
|
|
336
238
|
|
|
337
|
-
|
|
338
|
-
// variant is a compile error here rather than a silent fall-through to npm.
|
|
339
|
-
const latest =
|
|
340
|
-
vs.type === "npm_package"
|
|
341
|
-
? await fetchLatestVersion(vs.name, vs.dist_tag)
|
|
342
|
-
: await fetchLatestGithubRelease(vs.repo);
|
|
239
|
+
const latest = await fetchLatestVersion(vs.name, vs.dist_tag);
|
|
343
240
|
if (!latest) {
|
|
344
241
|
log.warn(`[singleton-tools-currency] ${tool.name}: cannot fetch latest — skip`);
|
|
345
242
|
recordEvent({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|