@knpkv/jcf-web 0.2.0

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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +315 -0
  3. package/dist/client/assets/AgentSettingsPanel-NhTzwiq6.js +1 -0
  4. package/dist/client/assets/Schema-DUFTUil2.js +2 -0
  5. package/dist/client/assets/UrlParams-ZN2s8OM_.js +6 -0
  6. package/dist/client/assets/agentSettings-nw9lCgBQ.js +1 -0
  7. package/dist/client/assets/api-BQCWhiKV.js +4 -0
  8. package/dist/client/assets/decoding-CebF86HD.js +1 -0
  9. package/dist/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  10. package/dist/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  11. package/dist/client/assets/index-Ch0W9DhA.css +1 -0
  12. package/dist/client/assets/index-N2vhoKYl.js +51 -0
  13. package/dist/client/assets/rolldown-runtime-hePW80VL.js +1 -0
  14. package/dist/client/assets/transport-AcByYfLh.js +1 -0
  15. package/dist/client/index.html +19 -0
  16. package/dist/index.d.ts +11 -0
  17. package/dist/index.js +12 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/main.d.ts +2 -0
  20. package/dist/main.js +32 -0
  21. package/dist/main.js.map +1 -0
  22. package/dist/server/Api.d.ts +789 -0
  23. package/dist/server/Api.js +98 -0
  24. package/dist/server/Api.js.map +1 -0
  25. package/dist/server/Confirm.d.ts +94 -0
  26. package/dist/server/Confirm.js +243 -0
  27. package/dist/server/Confirm.js.map +1 -0
  28. package/dist/server/Handlers.d.ts +14 -0
  29. package/dist/server/Handlers.js +157 -0
  30. package/dist/server/Handlers.js.map +1 -0
  31. package/dist/server/HttpApplication.d.ts +7 -0
  32. package/dist/server/HttpApplication.js +62 -0
  33. package/dist/server/HttpApplication.js.map +1 -0
  34. package/dist/server/OwnerSession.d.ts +62 -0
  35. package/dist/server/OwnerSession.js +219 -0
  36. package/dist/server/OwnerSession.js.map +1 -0
  37. package/dist/server/RowDescriptions.d.ts +19 -0
  38. package/dist/server/RowDescriptions.js +42 -0
  39. package/dist/server/RowDescriptions.js.map +1 -0
  40. package/dist/server/SavedEntryOperations.d.ts +27 -0
  41. package/dist/server/SavedEntryOperations.js +171 -0
  42. package/dist/server/SavedEntryOperations.js.map +1 -0
  43. package/dist/server/Server.d.ts +13 -0
  44. package/dist/server/Server.js +29 -0
  45. package/dist/server/Server.js.map +1 -0
  46. package/dist/server/WeekPlan.d.ts +110 -0
  47. package/dist/server/WeekPlan.js +258 -0
  48. package/dist/server/WeekPlan.js.map +1 -0
  49. package/dist/server/WeekPlans.d.ts +30 -0
  50. package/dist/server/WeekPlans.js +190 -0
  51. package/dist/server/WeekPlans.js.map +1 -0
  52. package/dist/server/WeekRead.d.ts +435 -0
  53. package/dist/server/WeekRead.js +189 -0
  54. package/dist/server/WeekRead.js.map +1 -0
  55. package/dist/shared/contracts.d.ts +901 -0
  56. package/dist/shared/contracts.js +365 -0
  57. package/dist/shared/contracts.js.map +1 -0
  58. package/dist/shared/writePlanning.d.ts +6 -0
  59. package/dist/shared/writePlanning.js +19 -0
  60. package/dist/shared/writePlanning.js.map +1 -0
  61. package/dist/tsconfig.tsbuildinfo +1 -0
  62. package/package.json +60 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Handlers.js","sourceRoot":"","sources":["../../src/server/Handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /** The same authenticated routes, bootstrap exchange and static client in production and tests. */
2
+ import { FileSystem, Layer, Path } from "effect";
3
+ import { HttpRouter } from "effect/http";
4
+ /** True only when `candidate` is `root` itself or one of its path-segment descendants. */
5
+ export declare const isWithinDirectory: (path: Path.Path, root: string, candidate: string) => boolean;
6
+ /** Supply engine services and owner secrets; listener and platform belong to the executable. */
7
+ export declare const application: Layer.Layer<never, never, import("@knpkv/jira-clockify/services/ConfigService.js").ConfigService | import("effect/Crypto").Crypto | FileSystem.FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | HttpRouter.HttpRouter | import("@knpkv/jira-clockify/services/IssueFacts.js").IssueFacts | import("@knpkv/jira-api-client").JiraApiClient | import("@knpkv/jira-cli/JiraAuth").JiraAuth | import("./OwnerSession.js").OwnerSessionSecrets | Path.Path | import("@knpkv/jira-clockify/services/ReconcileService.js").ReconcileService | HttpRouter.Request<"Error", import("effect/PlatformError").BadArgument> | HttpRouter.Request<"Error", import("effect/http/HttpBody").HttpBodyError> | HttpRouter.Request<"Error", import("effect/PlatformError").PlatformError> | HttpRouter.Request<"Requires", FileSystem.FileSystem> | HttpRouter.Request<"Requires", import("./OwnerSession.js").OwnerSessionSecrets> | HttpRouter.Request<"Requires", Path.Path> | import("@knpkv/jira-clockify/services/SavedEntries.js").SavedEntries | import("@knpkv/jira-clockify/services/SessionAttributor.js").SessionAttributor>;
@@ -0,0 +1,62 @@
1
+ /** The same authenticated routes, bootstrap exchange and static client in production and tests. */
2
+ import { Effect, FileSystem, Layer, Path } from "effect";
3
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
4
+ import { HttpApiBuilder } from "effect/http-api";
5
+ import { JcfWebApi } from "./Api.js";
6
+ import { ConfigLive, EntriesLive, RowsLive, WeekLive } from "./Handlers.js";
7
+ import { ownerSessionAuthLayer, OwnerSessionBootstrapRouter } from "./OwnerSession.js";
8
+ import { layer as weekPlansLayer } from "./WeekPlans.js";
9
+ const mimeTypes = new Map([
10
+ [".css", "text/css"],
11
+ [".html", "text/html"],
12
+ [".ico", "image/x-icon"],
13
+ [".js", "application/javascript"],
14
+ [".json", "application/json"],
15
+ [".map", "application/json"],
16
+ [".svg", "image/svg+xml"],
17
+ [".woff2", "font/woff2"]
18
+ ]);
19
+ /** True only when `candidate` is `root` itself or one of its path-segment descendants. */
20
+ export const isWithinDirectory = (path, root, candidate) => {
21
+ const relative = path.relative(root, candidate);
22
+ return relative === "" || (!path.isAbsolute(relative) && relative !== ".." &&
23
+ !relative.startsWith(`..${path.sep}`));
24
+ };
25
+ /** The built client, served from the same origin so the session cookie applies to both. */
26
+ const serveStatic = Effect.gen(function* () {
27
+ const request = yield* HttpServerRequest.HttpServerRequest;
28
+ const fileSystem = yield* FileSystem.FileSystem;
29
+ const path = yield* Path.Path;
30
+ const requested = decodeURIComponent(new URL(request.url, "http://localhost").pathname).replace(/^\/+/u, "");
31
+ const here = yield* path.fromFileUrl(new URL(".", import.meta.url));
32
+ const staticDirectory = path.resolve(here, "../../dist/client");
33
+ const resolved = path.resolve(staticDirectory, requested === "" ? "index.html" : requested);
34
+ // A resolved path that escaped the directory is a traversal attempt, not a missing file.
35
+ if (!isWithinDirectory(path, staticDirectory, resolved)) {
36
+ return HttpServerResponse.text("Forbidden", { status: 403 });
37
+ }
38
+ if (yield* fileSystem.exists(resolved)) {
39
+ const info = yield* fileSystem.stat(resolved);
40
+ if (info.type === "File") {
41
+ return HttpServerResponse.uint8Array(yield* fileSystem.readFile(resolved), {
42
+ headers: { "content-type": mimeTypes.get(path.extname(resolved)) ?? "application/octet-stream" },
43
+ status: 200
44
+ });
45
+ }
46
+ }
47
+ const index = path.join(staticDirectory, "index.html");
48
+ if (yield* fileSystem.exists(index)) {
49
+ return HttpServerResponse.uint8Array(yield* fileSystem.readFile(index), {
50
+ headers: { "content-type": "text/html" },
51
+ status: 200
52
+ });
53
+ }
54
+ return HttpServerResponse.text("The client has not been built. Run: pnpm --filter @knpkv/jcf-web build", {
55
+ status: 404
56
+ });
57
+ });
58
+ const StaticRouter = HttpRouter.use((router) => router.add("GET", "/*", serveStatic));
59
+ const ApiRoutes = HttpApiBuilder.layer(JcfWebApi).pipe(Layer.provide(Layer.mergeAll(WeekLive, RowsLive, ConfigLive, EntriesLive)), Layer.provide(ownerSessionAuthLayer), Layer.provide(weekPlansLayer));
60
+ /** Supply engine services and owner secrets; listener and platform belong to the executable. */
61
+ export const application = Layer.mergeAll(ApiRoutes, OwnerSessionBootstrapRouter, StaticRouter).pipe(Layer.orDie);
62
+ //# sourceMappingURL=HttpApplication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpApplication.js","sourceRoot":"","sources":["../../src/server/HttpApplication.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { CsrfToken, PairingCode, SessionToken } from "@knpkv/browser-pairing/schema";
2
+ import { Context, Effect, Layer, Redacted, Ref, Schema } from "effect";
3
+ import { HttpRouter } from "effect/http";
4
+ import { ForbiddenApiError, OwnerSessionAuth, UnauthorizedApiError } from "./Api.js";
5
+ export interface BootstrapAttemptState {
6
+ readonly failedAttempts: number;
7
+ readonly inFlight: number;
8
+ }
9
+ export interface OwnerSessionSecretsContract {
10
+ /** The origin this server considers itself to be. A request's Host header is never authoritative. */
11
+ readonly authorityOrigin: string;
12
+ /**
13
+ * The origin the browser is expected to present. Same as the authority in production; the Vite
14
+ * dev server when it proxies in front of it, because that is the origin the page runs on.
15
+ */
16
+ readonly browserOrigin: string;
17
+ readonly bootstrapAvailable: Ref.Ref<boolean>;
18
+ readonly bootstrapAttemptState: Ref.Ref<BootstrapAttemptState>;
19
+ readonly bootstrapExpiresAtMillis: Ref.Ref<number | undefined>;
20
+ readonly bootstrapToken: Redacted.Redacted<PairingCode>;
21
+ readonly csrfToken: Redacted.Redacted<CsrfToken>;
22
+ readonly ownerToken: Redacted.Redacted<SessionToken>;
23
+ }
24
+ declare const OwnerSessionSecrets_base: Context.ServiceClass<OwnerSessionSecrets, "@knpkv/jcf-web/OwnerSessionSecrets", OwnerSessionSecretsContract>;
25
+ export declare class OwnerSessionSecrets extends OwnerSessionSecrets_base {
26
+ }
27
+ declare const UnsafeServerOriginError_base: Schema.Class<UnsafeServerOriginError, Schema.TaggedStruct<"UnsafeServerOriginError", {
28
+ readonly message: Schema.String;
29
+ readonly origin: Schema.String;
30
+ }>, import("effect/Cause").YieldableError>;
31
+ export declare class UnsafeServerOriginError extends UnsafeServerOriginError_base {
32
+ }
33
+ export declare const isLoopbackHostname: (hostname: string) => boolean;
34
+ export declare const ownerSessionOrigin: (hostname: string, port: number) => string;
35
+ export declare const requireLoopbackOrigin: (origin: string) => Effect.Effect<string, UnsafeServerOriginError, never>;
36
+ /** The advertised origin: the bound server, or the dev server that proxies to it. */
37
+ export declare const resolvePublicOrigin: (configuredOrigin: string | undefined, authorityOrigin: string) => Effect.Effect<string, UnsafeServerOriginError, never>;
38
+ export declare const makeOwnerSessionSecrets: (authorityOrigin: string, configuredPublicOrigin?: string | undefined) => Effect.Effect<OwnerSessionSecretsContract, import("@knpkv/browser-pairing").BrowserPairingError | UnsafeServerOriginError, import("effect/Crypto").Crypto>;
39
+ /** Start the bootstrap clock. Called once the server is actually listening, never before. */
40
+ export declare const activateOwnerSessionBootstrap: (secrets: OwnerSessionSecretsContract) => Effect.Effect<void, import("@knpkv/browser-pairing").BrowserPairingError, never>;
41
+ /**
42
+ * The URL to open. The code rides in the fragment, which browsers do not send to servers and
43
+ * proxies do not log, and the page strips it from the address bar once it has been spent.
44
+ */
45
+ export declare const ownerSessionUrl: (publicOrigin: string, secrets: Pick<OwnerSessionSecretsContract, "bootstrapToken">) => string;
46
+ export declare const ownerSessionCookie: (secrets: Pick<OwnerSessionSecretsContract, "ownerToken">) => string;
47
+ interface OwnerRequest {
48
+ readonly credential: string;
49
+ readonly csrfToken: string | undefined;
50
+ readonly fetchSite: string | undefined;
51
+ readonly method: string;
52
+ readonly origin: string | undefined;
53
+ }
54
+ export declare const authorizeOwnerRequest: (request: OwnerRequest, secrets: OwnerSessionSecretsContract) => Effect.Effect<undefined, ForbiddenApiError | UnauthorizedApiError, never>;
55
+ export declare const authorizeBootstrapRequest: (request: {
56
+ readonly authorization: string | undefined;
57
+ readonly origin: string | undefined;
58
+ }, secrets: OwnerSessionSecretsContract) => Effect.Effect<undefined, ForbiddenApiError | UnauthorizedApiError, never>;
59
+ export declare const ownerSessionAuthLayer: Layer.Layer<OwnerSessionAuth, never, OwnerSessionSecrets>;
60
+ /** The one route outside the authenticated API: spending the printed code for a session. */
61
+ export declare const OwnerSessionBootstrapRouter: Layer.Layer<never, never, HttpRouter.HttpRouter | HttpRouter.Request<"Error", import("effect/http/HttpBody").HttpBodyError> | HttpRouter.Request<"Requires", OwnerSessionSecrets>>;
62
+ export {};
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Who may talk to this server.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **One process, one operator.** The server binds a loopback address and mints a session token for
7
+ * itself at startup. There is no user table and no login: the person who can read the terminal is
8
+ * the person who gets in, which is exactly the authority a tool that writes to their own timesheet
9
+ * should have.
10
+ * - **The URL is the handshake.** The bootstrap code is printed once, in a fragment the browser
11
+ * never sends upstream, and is spent the first time it is exchanged for the session cookie.
12
+ * - **A read needs the cookie and must not be a browser cross-origin request; a write also needs the
13
+ * expected origin and CSRF header.** Another page in the same browser can make the browser send a
14
+ * cookie, but Fetch Metadata keeps that page from starting agent-backed reads without an Origin.
15
+ *
16
+ * The rules are those of `@knpkv/codecommit-web`'s owner session, over the same
17
+ * `@knpkv/browser-pairing` credential primitives. They are re-stated here rather than imported
18
+ * because that module is private to that application; the shared home for them is a later job than
19
+ * this one, and duplicating the *policy* while sharing the *primitives* is the smaller of the two
20
+ * mistakes available.
21
+ *
22
+ * @module
23
+ */
24
+ import { credentialValuesEqual, decideOneTimeCredential, expiresAt, issueCsrfToken, issuePairingCode, issueSessionToken, serializeCredentialCookie } from "@knpkv/browser-pairing";
25
+ import { Clock, Context, Effect, Layer, Redacted, Ref, Schema } from "effect";
26
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
27
+ import { ForbiddenApiError, OwnerSessionAuth, UnauthorizedApiError } from "./Api.js";
28
+ /** How long the printed bootstrap URL stays usable. Long enough to click, short enough to forget. */
29
+ const BOOTSTRAP_LIFETIME_MILLIS = 60_000;
30
+ /**
31
+ * Failed bootstrap exchanges before the exchange closes for the life of the process.
32
+ *
33
+ * The code is high-entropy, so this is not what makes guessing hard — it is what stops a process
34
+ * that is being guessed at from staying open all afternoon.
35
+ */
36
+ const MAX_BOOTSTRAP_FAILURES = 5;
37
+ /** Methods that only read. They need the session, but no origin and no CSRF token. */
38
+ const safeMethods = new Set(["GET", "HEAD", "OPTIONS"]);
39
+ /**
40
+ * The one origin other than the bound server that may hold a session: the Vite dev server, which
41
+ * proxies both the API and the bootstrap exchange, so the browser stays on a single origin.
42
+ */
43
+ const devPublicOrigin = "http://localhost:5173";
44
+ export class OwnerSessionSecrets extends Context.Service()("@knpkv/jcf-web/OwnerSessionSecrets") {
45
+ }
46
+ export class UnsafeServerOriginError extends Schema.TaggedError()("UnsafeServerOriginError", { message: Schema.String, origin: Schema.String }) {
47
+ }
48
+ export const isLoopbackHostname = (hostname) => hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1" || hostname === "[::1]";
49
+ export const ownerSessionOrigin = (hostname, port) => `http://${hostname === "::1" ? "[::1]" : hostname}:${port}`;
50
+ export const requireLoopbackOrigin = Effect.fn("OwnerSession.requireLoopbackOrigin")(function* (origin) {
51
+ const url = yield* Effect.try({
52
+ catch: () => new UnsafeServerOriginError({ message: "jcf-web needs a valid HTTP origin", origin }),
53
+ try: () => new URL(origin)
54
+ });
55
+ if (url.protocol !== "http:" || !isLoopbackHostname(url.hostname) || url.search !== "" || url.hash !== "") {
56
+ return yield* new UnsafeServerOriginError({
57
+ message: "jcf-web may only be reached over HTTP on a loopback host",
58
+ origin
59
+ });
60
+ }
61
+ return url.origin;
62
+ });
63
+ /** The advertised origin: the bound server, or the dev server that proxies to it. */
64
+ export const resolvePublicOrigin = Effect.fn("OwnerSession.resolvePublicOrigin")(function* (configuredOrigin, authorityOrigin) {
65
+ const authority = yield* requireLoopbackOrigin(authorityOrigin);
66
+ if (configuredOrigin === undefined)
67
+ return authority;
68
+ const configured = yield* requireLoopbackOrigin(configuredOrigin);
69
+ if (configured !== authority && configured !== devPublicOrigin) {
70
+ return yield* new UnsafeServerOriginError({
71
+ message: "jcf-web's public origin must be the bound server or its dev proxy",
72
+ origin: configuredOrigin
73
+ });
74
+ }
75
+ return configured;
76
+ });
77
+ export const makeOwnerSessionSecrets = Effect.fn("OwnerSession.makeSecrets")(function* (authorityOrigin, configuredPublicOrigin) {
78
+ const validated = yield* requireLoopbackOrigin(authorityOrigin);
79
+ const browserOrigin = yield* resolvePublicOrigin(configuredPublicOrigin, validated);
80
+ const [ownerToken, csrfToken, bootstrapToken] = yield* Effect.all([
81
+ issueSessionToken(),
82
+ issueCsrfToken(),
83
+ issuePairingCode()
84
+ ]);
85
+ return OwnerSessionSecrets.of({
86
+ authorityOrigin: validated,
87
+ bootstrapAttemptState: yield* Ref.make({ failedAttempts: 0, inFlight: 0 }),
88
+ bootstrapAvailable: yield* Ref.make(true),
89
+ bootstrapExpiresAtMillis: yield* Ref.make(undefined),
90
+ bootstrapToken,
91
+ browserOrigin,
92
+ csrfToken,
93
+ ownerToken
94
+ });
95
+ });
96
+ /** Start the bootstrap clock. Called once the server is actually listening, never before. */
97
+ export const activateOwnerSessionBootstrap = Effect.fn("OwnerSession.activateBootstrap")(function* (secrets) {
98
+ const now = yield* Clock.currentTimeMillis;
99
+ yield* Ref.set(secrets.bootstrapExpiresAtMillis, yield* expiresAt(now, BOOTSTRAP_LIFETIME_MILLIS));
100
+ });
101
+ /**
102
+ * The URL to open. The code rides in the fragment, which browsers do not send to servers and
103
+ * proxies do not log, and the page strips it from the address bar once it has been spent.
104
+ */
105
+ export const ownerSessionUrl = (publicOrigin, secrets) => `${publicOrigin.replace(/\/+$/u, "")}/#bootstrap_token=${encodeURIComponent(Redacted.value(secrets.bootstrapToken))}`;
106
+ export const ownerSessionCookie = (secrets) => serializeCredentialCookie(secrets.ownerToken, {
107
+ httpOnly: true,
108
+ name: "jcf_owner",
109
+ path: "/api",
110
+ sameSite: "strict",
111
+ secure: false
112
+ });
113
+ const fetchSites = new Set(["cross-site", "none", "same-origin", "same-site"]);
114
+ export const authorizeOwnerRequest = Effect.fn("OwnerSession.authorizeRequest")(function* (request, secrets) {
115
+ if (credentialValuesEqual(request.credential, Redacted.value(secrets.ownerToken)) !== true) {
116
+ return yield* new UnauthorizedApiError({ message: "Missing or invalid owner session" });
117
+ }
118
+ const sameOrigin = request.origin !== undefined && request.origin === secrets.browserOrigin;
119
+ if (request.origin !== undefined && !sameOrigin) {
120
+ return yield* new ForbiddenApiError({ message: "Request origin does not match this jcf-web server" });
121
+ }
122
+ if (request.fetchSite !== undefined && !fetchSites.has(request.fetchSite)) {
123
+ return yield* new ForbiddenApiError({ message: "Request carries invalid browser site metadata" });
124
+ }
125
+ if (request.fetchSite === "cross-site" ||
126
+ (!sameOrigin && (request.fetchSite === "same-site" || request.fetchSite === "none"))) {
127
+ return yield* new ForbiddenApiError({ message: "Browser request is not from this jcf-web page" });
128
+ }
129
+ // An explicit client such as curl carries neither Origin nor Fetch Metadata and remains allowed:
130
+ // it still has to present the process-scoped session cookie. Browser-marked cross-origin reads
131
+ // are rejected before their handler can start provider or agent work.
132
+ if (safeMethods.has(request.method.toUpperCase()))
133
+ return;
134
+ if (!sameOrigin) {
135
+ return yield* new ForbiddenApiError({ message: "Mutation origin does not match this jcf-web server" });
136
+ }
137
+ const csrfMatches = request.csrfToken !== undefined &&
138
+ credentialValuesEqual(request.csrfToken, Redacted.value(secrets.csrfToken)) === true;
139
+ if (!csrfMatches)
140
+ return yield* new ForbiddenApiError({ message: "Missing or invalid CSRF token" });
141
+ });
142
+ const bearerToken = (authorization) => authorization !== undefined && authorization.startsWith("Bearer ")
143
+ ? authorization.slice("Bearer ".length)
144
+ : undefined;
145
+ export const authorizeBootstrapRequest = Effect.fn("OwnerSession.authorizeBootstrap")(function* (request, secrets) {
146
+ if (request.origin !== secrets.browserOrigin) {
147
+ return yield* new ForbiddenApiError({ message: "Bootstrap origin does not match this jcf-web server" });
148
+ }
149
+ const supplied = bearerToken(request.authorization);
150
+ // Counted before the code is even compared, so a burst of parallel guesses cannot all pass the
151
+ // check together and then be counted afterwards.
152
+ const admit = Ref.modify(secrets.bootstrapAttemptState, (state) => {
153
+ if (state.failedAttempts + state.inFlight >= MAX_BOOTSTRAP_FAILURES)
154
+ return ["unavailable", state];
155
+ const matches = supplied !== undefined &&
156
+ credentialValuesEqual(supplied, Redacted.value(secrets.bootstrapToken)) === true;
157
+ if (!matches) {
158
+ return ["invalid", {
159
+ failedAttempts: Math.min(MAX_BOOTSTRAP_FAILURES, state.failedAttempts + 1),
160
+ inFlight: state.inFlight
161
+ }];
162
+ }
163
+ return ["accepted", { ...state, inFlight: state.inFlight + 1 }];
164
+ }).pipe(Effect.flatMap((admission) => admission === "unavailable"
165
+ ? Effect.fail(new UnauthorizedApiError({ message: "Bootstrap is closed for this process" }))
166
+ : admission === "invalid"
167
+ ? Effect.fail(new UnauthorizedApiError({ message: "Missing or invalid bootstrap token" }))
168
+ : Effect.void));
169
+ return yield* Effect.acquireUseRelease(admit, () => Effect.gen(function* () {
170
+ const expiry = yield* Ref.get(secrets.bootstrapExpiresAtMillis);
171
+ if (expiry === undefined)
172
+ return yield* new UnauthorizedApiError({ message: "Bootstrap is not active yet" });
173
+ const now = yield* Clock.currentTimeMillis;
174
+ const decision = yield* Ref.modify(secrets.bootstrapAvailable, (available) => {
175
+ const next = decideOneTimeCredential({ consumedAt: available ? null : 0, expiresAt: expiry, revokedAt: null }, now);
176
+ return [next, next === "accepted" ? false : available];
177
+ });
178
+ if (decision === "accepted")
179
+ return;
180
+ const message = decision === "expired"
181
+ ? "Bootstrap token has expired — restart jcf-web for a fresh URL"
182
+ : decision === "consumed"
183
+ ? "Bootstrap token has already been used"
184
+ : "Bootstrap token state is invalid";
185
+ return yield* new UnauthorizedApiError({ message });
186
+ }), () => Ref.update(secrets.bootstrapAttemptState, (state) => ({ ...state, inFlight: Math.max(0, state.inFlight - 1) })));
187
+ });
188
+ export const ownerSessionAuthLayer = Layer.effect(OwnerSessionAuth, Effect.gen(function* () {
189
+ const secrets = yield* OwnerSessionSecrets;
190
+ return OwnerSessionAuth.of({
191
+ ownerCookie: Effect.fn("OwnerSession.ownerCookie")(function* (httpEffect, { credential }) {
192
+ const request = yield* HttpServerRequest.HttpServerRequest;
193
+ yield* authorizeOwnerRequest({
194
+ credential: Redacted.value(credential),
195
+ csrfToken: request.headers["x-csrf-token"],
196
+ fetchSite: request.headers["sec-fetch-site"],
197
+ method: request.method,
198
+ origin: request.headers.origin
199
+ }, secrets);
200
+ return yield* httpEffect;
201
+ })
202
+ });
203
+ }));
204
+ /** The one route outside the authenticated API: spending the printed code for a session. */
205
+ export const OwnerSessionBootstrapRouter = HttpRouter.use((router) => router.add("POST", "/auth/bootstrap", Effect.gen(function* () {
206
+ const secrets = yield* OwnerSessionSecrets;
207
+ const request = yield* HttpServerRequest.HttpServerRequest;
208
+ const result = yield* Effect.result(authorizeBootstrapRequest({ authorization: request.headers.authorization, origin: request.headers.origin }, secrets));
209
+ if (result._tag === "Failure") {
210
+ return HttpServerResponse.text(result.failure.message, {
211
+ status: result.failure._tag === "UnauthorizedApiError" ? 401 : 403
212
+ });
213
+ }
214
+ return yield* HttpServerResponse.json({ csrfToken: Redacted.value(secrets.csrfToken) }, {
215
+ headers: { "cache-control": "no-store", "set-cookie": ownerSessionCookie(secrets) },
216
+ status: 200
217
+ });
218
+ })));
219
+ //# sourceMappingURL=OwnerSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OwnerSession.js","sourceRoot":"","sources":["../../src/server/OwnerSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EAC1B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7E,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC/E,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAEpF,qGAAqG;AACrG,MAAM,yBAAyB,GAAG,MAAM,CAAA;AAExC;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG,CAAC,CAAA;AAEhC,sFAAsF;AACtF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;AAEvD;;;GAGG;AACH,MAAM,eAAe,GAAG,uBAAuB,CAAA;AAuB/C,MAAM,OAAO,mBAAoB,SAAQ,OAAO,CAAC,OAAO,EAGrD,CAAC,oCAAoC,CAAC;CAAG;AAE5C,MAAM,OAAO,uBAAwB,SAAQ,MAAM,CAAC,WAAW,EAA2B,CACxF,yBAAyB,EACzB,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAClD;CAAG;AAEJ,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAW,EAAE,CAC9D,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,OAAO,CAAA;AAEpG,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAE,IAAY,EAAU,EAAE,CAC3E,UAAU,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAA;AAE7D,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,EAAE,CAAC,oCAAoC,CAAC,CAClF,QAAQ,CAAC,EAAC,MAAc;IACtB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;QAC5B,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,uBAAuB,CAAC,EAAE,OAAO,EAAE,mCAAmC,EAAE,MAAM,EAAE,CAAC;QAClG,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC;KAC3B,CAAC,CAAA;IACF,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QAC1G,OAAO,KAAK,CAAC,CAAC,IAAI,uBAAuB,CAAC;YACxC,OAAO,EAAE,0DAA0D;YACnE,MAAM;SACP,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAA;AACnB,CAAC,CACF,CAAA;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,EAAE,CAAC,kCAAkC,CAAC,CAC9E,QAAQ,CAAC,EAAC,gBAAoC,EAAE,eAAuB;IACrE,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC/D,IAAI,gBAAgB,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACpD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IACjE,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,eAAe,EAAE,CAAC;QAC/D,OAAO,KAAK,CAAC,CAAC,IAAI,uBAAuB,CAAC;YACxC,OAAO,EAAE,mEAAmE;YAC5E,MAAM,EAAE,gBAAgB;SACzB,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC,CACF,CAAA;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,EAAE,CAAC,0BAA0B,CAAC,CAC1E,QAAQ,CAAC,EAAC,eAAuB,EAAE,sBAA+B;IAChE,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC/D,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAA;IACnF,MAAM,CAAC,UAAU,EAAE,SAAS,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;QAChE,iBAAiB,EAAE;QACnB,cAAc,EAAE;QAChB,gBAAgB,EAAE;KACnB,CAAC,CAAA;IACF,OAAO,mBAAmB,CAAC,EAAE,CAAC;QAC5B,eAAe,EAAE,SAAS;QAC1B,qBAAqB,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAwB,EAAE,cAAc,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QACjG,kBAAkB,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,wBAAwB,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAqB,SAAS,CAAC;QACxE,cAAc;QACd,aAAa;QACb,SAAS;QACT,UAAU;KACX,CAAC,CAAA;AACJ,CAAC,CACF,CAAA;AAED,6FAA6F;AAC7F,MAAM,CAAC,MAAM,6BAA6B,GAAG,MAAM,CAAC,EAAE,CAAC,gCAAgC,CAAC,CACtF,QAAQ,CAAC,EAAC,OAAoC;IAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAA;IAC1C,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAA;AACpG,CAAC,CACF,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,YAAoB,EACpB,OAA4D,EACpD,EAAE,CACV,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,qBAAqB,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,EAAE,CAAA;AAEvH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAwD,EAAU,EAAE,CACrG,yBAAyB,CAAC,OAAO,CAAC,UAAU,EAAE;IAC5C,QAAQ,EAAE,IAAI;IACd,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,KAAK;CACd,CAAC,CAAA;AAUJ,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAA;AAE9E,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAC7E,QAAQ,CAAC,EAAC,OAAqB,EAAE,OAAoC;IACnE,IAAI,qBAAqB,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3F,OAAO,KAAK,CAAC,CAAC,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC,CAAA;IACzF,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,aAAa,CAAA;IAC3F,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,mDAAmD,EAAE,CAAC,CAAA;IACvG,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1E,OAAO,KAAK,CAAC,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,+CAA+C,EAAE,CAAC,CAAA;IACnG,CAAC;IACD,IACE,OAAO,CAAC,SAAS,KAAK,YAAY;QAClC,CAAC,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,WAAW,IAAI,OAAO,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,EACpF,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,+CAA+C,EAAE,CAAC,CAAA;IACnG,CAAC;IACD,iGAAiG;IACjG,+FAA+F;IAC/F,sEAAsE;IACtE,IAAI,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAAE,OAAM;IACzD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,KAAK,CAAC,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,CAAA;IACxG,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS;QACjD,qBAAqB,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,IAAI,CAAA;IACtF,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC,CAAA;AACrG,CAAC,CACF,CAAA;AAID,MAAM,WAAW,GAAG,CAAC,aAAiC,EAAsB,EAAE,CAC5E,aAAa,KAAK,SAAS,IAAI,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC;IAChE,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;IACvC,CAAC,CAAC,SAAS,CAAA;AAEf,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,EAAE,CAAC,iCAAiC,CAAC,CACnF,QAAQ,CAAC,EACP,OAA4F,EAC5F,OAAoC;IAEpC,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC;QAC7C,OAAO,KAAK,CAAC,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,qDAAqD,EAAE,CAAC,CAAA;IACzG,CAAC;IACD,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IACnD,+FAA+F;IAC/F,iDAAiD;IACjD,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CACtB,OAAO,CAAC,qBAAqB,EAC7B,CAAC,KAAK,EAAwD,EAAE;QAC9D,IAAI,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,QAAQ,IAAI,sBAAsB;YAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;QAClG,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS;YACpC,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,KAAK,IAAI,CAAA;QAClF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,SAAS,EAAE;oBACjB,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC;oBAC1E,QAAQ,EAAE,KAAK,CAAC,QAAQ;iBACzB,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,CAAC,UAAU,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,CAAA;IACjE,CAAC,CACF,CAAC,IAAI,CACJ,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAC3B,SAAS,KAAK,aAAa;QACzB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC,CAAC;QAC5F,CAAC,CAAC,SAAS,KAAK,SAAS;YACzB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC,CAAC;YAC1F,CAAC,CAAC,MAAM,CAAC,IAAI,CAChB,CACF,CAAA;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,iBAAiB,CACpC,KAAK,EACL,GAAG,EAAE,CACH,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAA;QAC/D,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC,CAAC,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC,CAAA;QAC5G,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAA;QAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,SAAS,EAAE,EAAE;YAC3E,MAAM,IAAI,GAAG,uBAAuB,CAClC,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,EACxE,GAAG,CACJ,CAAA;YACD,OAAO,CAAC,IAAI,EAAE,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QACxD,CAAC,CAAC,CAAA;QACF,IAAI,QAAQ,KAAK,UAAU;YAAE,OAAM;QACnC,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS;YACpC,CAAC,CAAC,+DAA+D;YACjE,CAAC,CAAC,QAAQ,KAAK,UAAU;gBACzB,CAAC,CAAC,uCAAuC;gBACzC,CAAC,CAAC,kCAAkC,CAAA;QACtC,OAAO,KAAK,CAAC,CAAC,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IACrD,CAAC,CAAC,EACJ,GAAG,EAAE,CACH,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAClH,CAAA;AACH,CAAC,CACF,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC,MAAM,CAC/C,gBAAgB,EAChB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAA;IAC1C,OAAO,gBAAgB,CAAC,EAAE,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,0BAA0B,CAAC,CAChD,QAAQ,CAAC,EAAC,UAAU,EAAE,EAAE,UAAU,EAAE;YAClC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAA;YAC1D,KAAK,CAAC,CAAC,qBAAqB,CAC1B;gBACE,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC;gBACtC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC;gBAC1C,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;gBAC5C,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM;aAC/B,EACD,OAAO,CACR,CAAA;YACD,OAAO,KAAK,CAAC,CAAC,UAAU,CAAA;QAC1B,CAAC,CACF;KACF,CAAC,CAAA;AACJ,CAAC,CAAC,CACH,CAAA;AAED,4FAA4F;AAC5F,MAAM,CAAC,MAAM,2BAA2B,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACnE,MAAM,CAAC,GAAG,CACR,MAAM,EACN,iBAAiB,EACjB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAA;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAA;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CACjC,yBAAyB,CACvB,EAAE,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAChF,OAAO,CACR,CACF,CAAA;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE;YACrD,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,sBAAsB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;SACnE,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE;QACtF,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACnF,MAAM,EAAE,GAAG;KACZ,CAAC,CAAA;AACJ,CAAC,CAAC,CACH,CACF,CAAA"}
@@ -0,0 +1,19 @@
1
+ /** Row-level suggestions use only the evidence and title retained by a scan. */
2
+ import type { ConfigService, ReconcileService } from "@knpkv/jira-clockify";
3
+ import type { SessionAgentSettings } from "@knpkv/jira-clockify/agent/agentSettings.js";
4
+ import { Cache, Effect } from "effect";
5
+ import { ApiError, PlanExpiredError } from "../shared/contracts.js";
6
+ import type { HeldPlan } from "./WeekPlan.js";
7
+ export declare const agentSettingsKey: (settings: SessionAgentSettings) => string;
8
+ export interface DescriptionKey {
9
+ readonly rowId: string;
10
+ readonly settings: string;
11
+ readonly revision: number;
12
+ }
13
+ /** One cache per retained scan; concurrent selections share the same prompt-only operation. */
14
+ export declare const makeRowDescriptions: (options: {
15
+ readonly plan: Effect.Effect<HeldPlan | undefined>;
16
+ readonly config: Pick<ConfigService.ConfigServiceContract, "get">;
17
+ readonly reconcile: Pick<ReconcileService.ReconcileServiceContract, "describeProposals">;
18
+ readonly revision: Effect.Effect<number>;
19
+ }) => Effect.Effect<Cache.Cache<DescriptionKey, string | null, ApiError | PlanExpiredError, never>, never, never>;
@@ -0,0 +1,42 @@
1
+ import { Cache, Effect, Exit } from "effect";
2
+ import { ApiError, PlanExpiredError } from "../shared/contracts.js";
3
+ export const agentSettingsKey = (settings) => JSON.stringify([settings.provider, settings.model, settings.effort]);
4
+ /** One cache per retained scan; concurrent selections share the same prompt-only operation. */
5
+ export const makeRowDescriptions = Effect.fn("RowDescriptions.make")(function* (options) {
6
+ const lookup = Effect.fn("RowDescriptions.lookup")(function* (key) {
7
+ const plan = yield* options.plan;
8
+ const evidence = plan?.evidence.get(key.rowId);
9
+ if (plan === undefined || evidence === undefined) {
10
+ return yield* new PlanExpiredError({ message: "That row is not part of this week — reload it" });
11
+ }
12
+ const unchanged = Effect.gen(function* () {
13
+ const settings = (yield* options.config.get).sessionAgent;
14
+ if (agentSettingsKey(settings) !== key.settings || (yield* options.revision) !== key.revision) {
15
+ return yield* new ApiError({ message: "Agent settings changed while generating the description. Retry." });
16
+ }
17
+ });
18
+ yield* unchanged;
19
+ const title = plan.ownership.facts.get(evidence.proposal.ticketKey)?.title;
20
+ let unavailable = null;
21
+ const notes = yield* options.reconcile.describeProposals({
22
+ proposals: [evidence.proposal],
23
+ digests: plan.report.digests,
24
+ summaries: new Map(title == null ? [] : [[evidence.proposal.ticketKey, title]]),
25
+ onUnavailable: (message) => Effect.sync(() => {
26
+ unavailable = message;
27
+ })
28
+ });
29
+ // The engine reads its own config per call. Reject any result crossing a settings update,
30
+ // including a change away and back through HTTP, instead of storing it under an earlier key.
31
+ yield* unchanged;
32
+ if (unavailable !== null) {
33
+ return yield* new ApiError({ message: `Could not generate the description: ${unavailable}. Retry.` });
34
+ }
35
+ return notes[0] ?? null;
36
+ });
37
+ return yield* Cache.makeWith(lookup, {
38
+ capacity: 512,
39
+ timeToLive: (exit) => Exit.isSuccess(exit) ? Infinity : 0
40
+ });
41
+ });
42
+ //# sourceMappingURL=RowDescriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RowDescriptions.js","sourceRoot":"","sources":["../../src/server/RowDescriptions.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAGnE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAA8B,EAAU,EAAE,CACzE,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;AAQtE,+FAA+F;AAC/F,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,QAAQ,CAAC,EAAC,OAK9E;IACC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EAAC,GAAmB;QAC9E,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAA;QAChC,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC9C,IAAI,IAAI,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACjD,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC,EAAE,OAAO,EAAE,+CAA+C,EAAE,CAAC,CAAA;QAClG,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACpC,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAA;YACzD,IAAI,gBAAgB,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC;gBAC9F,OAAO,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,EAAE,OAAO,EAAE,iEAAiE,EAAE,CAAC,CAAA;YAC5G,CAAC;QACH,CAAC,CAAC,CAAA;QACF,KAAK,CAAC,CAAC,SAAS,CAAA;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAA;QAC1E,IAAI,WAAW,GAAkB,IAAI,CAAA;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,iBAAiB,CAAC;YACvD,SAAS,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC9B,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,SAAS,EAAE,IAAI,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAC/E,aAAa,EAAE,CAAC,OAAO,EAAE,EAAE,CACzB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;gBACf,WAAW,GAAG,OAAO,CAAA;YACvB,CAAC,CAAC;SACL,CAAC,CAAA;QACF,0FAA0F;QAC1F,6FAA6F;QAC7F,KAAK,CAAC,CAAC,SAAS,CAAA;QAChB,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,EAAE,OAAO,EAAE,uCAAuC,WAAW,UAAU,EAAE,CAAC,CAAA;QACvG,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;IACzB,CAAC,CAAC,CAAA;IACF,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE;QACnC,QAAQ,EAAE,GAAG;QACb,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;KAC1D,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,27 @@
1
+ /** Saved provider entries resolve only through retained weeks; generation consumes retained digests. */
2
+ import type { SavedEntries, SessionAttributor } from "@knpkv/jira-clockify";
3
+ import { ReconcileService } from "@knpkv/jira-clockify";
4
+ import { Effect } from "effect";
5
+ import { ApiError, type DescribeSavedEntryRequest, PlanExpiredError, ProposalRejectedError, type SavedEntry, type UpdateSavedEntryRequest } from "../shared/contracts.js";
6
+ import { type HeldPlan } from "./WeekPlan.js";
7
+ /** Metadata must agree with its retained provider. A description edit may change its ticket prefix. */
8
+ export declare const findSavedEntry: (plan: HeldPlan, request: DescribeSavedEntryRequest) => SavedEntry | undefined;
9
+ /** Provider refreshes keep the handle only when every expected field still describes the same entry. */
10
+ export declare const retainedEntryRevision: (plan: HeldPlan, entry: ReconcileService.RecordedEntry, fresh: string) => string;
11
+ /** Rebuild provider slices and proposal deltas locally so a failed totals refresh cannot undo a save. */
12
+ export declare const replaceSavedEntry: (plan: HeldPlan, entry: SavedEntry) => HeldPlan;
13
+ /** A changed interval stays in the retained week and is at most one day. Long unchanged entries remain editable. */
14
+ export declare const validateSavedUpdate: (plan: HeldPlan, expected: SavedEntry, request: UpdateSavedEntryRequest) => Effect.Effect<undefined, ProposalRejectedError, never>;
15
+ export declare const savedEntryFailure: (error: SavedEntries.SavedEntryError) => ApiError | PlanExpiredError | ProposalRejectedError;
16
+ /** Only individually overlapping sessions qualify. An unkeyed entry with several matches is ambiguous. */
17
+ export declare const describeSavedEntry: (options: {
18
+ readonly plan: HeldPlan;
19
+ readonly entry: SavedEntry;
20
+ readonly attributor: SessionAttributor.SessionAttributorContract;
21
+ }) => Effect.Effect<{
22
+ note: null;
23
+ sessionCount: number;
24
+ } | {
25
+ note: string;
26
+ sessionCount: number;
27
+ }, ApiError, never>;