@teamscala/service-boot 0.23.503 → 0.23.505
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 +1 -1
- package/src/agreement-gate/signing-shell.test.ts +9 -15
- package/src/agreement-gate.test.ts +8 -15
- package/src/asset-proxy.test.ts +9 -9
- package/src/attach-portal-members.test.ts +9 -13
- package/src/attach-transfers-upload.test.ts +9 -10
- package/src/auth-gate.test.ts +8 -7
- package/src/build-client-assets/manifest.test.ts +8 -14
- package/src/build-client-assets/source-candidates.test.ts +9 -11
- package/src/build-client-assets/sourcemaps-upload.test.ts +8 -15
- package/src/build-client-assets/styles-build.test.ts +9 -15
- package/src/build-client-assets.test.ts +8 -12
- package/src/capability-slug.test.ts +9 -0
- package/src/compose-page-title.test.ts +8 -9
- package/src/content-gate/catalog.test.ts +8 -8
- package/src/content-gate/token.test.ts +8 -13
- package/src/content-page-floor.test.ts +9 -11
- package/src/csrf-body-limit.test.ts +9 -15
- package/src/handlers/messaging-send-reconcile-logic.test.ts +9 -11
- package/src/handlers/messaging-send-recording-logic.test.ts +9 -9
- package/src/handlers/messaging-send-recording-watch.test.ts +9 -11
- package/src/handlers/messaging-send-sweep-selection.test.ts +9 -7
- package/src/handlers/platform-fault-escalation/probe.test.ts +9 -10
- package/src/handlers/platform-fault-escalation-refresh.test.ts +9 -22
- package/src/handlers/platform-fault-escalation.test.ts +9 -10
- package/src/handlers/scala-tools-currency-watch.test.ts +9 -0
- package/src/handlers/singleton-tools-currency-watch.test.ts +9 -0
- package/src/handlers/whatsapp-liveness-handler.test.ts +9 -12
- package/src/handlers/whatsapp-liveness.test.ts +9 -9
- package/src/interpolate-meta.ts +123 -0
- package/src/is-react-element-type.test.ts +8 -9
- package/src/loader-errors.ts +41 -0
- package/src/module-not-found.test.ts +8 -12
- package/src/mount-orpc-router.test.ts +8 -9
- package/src/page-server-orpc/server-client.test.ts +8 -17
- package/src/read-html-template.test.ts +8 -12
- package/src/register-registry-page-route/apply-page-branding.test.ts +8 -19
- package/src/register-registry-page-route/captured-loader-config.test.ts +9 -13
- package/src/register-registry-page-route/graph-cache.test.ts +9 -12
- package/src/register-registry-page-route/live-loader-config.test.ts +8 -24
- package/src/register-registry-page-route/meta-title.ts +1 -1
- package/src/register-registry-page-route/page-data-shared.test.ts +9 -12
- package/src/register-registry-page-route/public-shell-page-data.test.ts +8 -19
- package/src/register-registry-page-route/resolve-entity-title.test.ts +8 -7
- package/src/register-registry-page-route/resolve-page-data.test.ts +9 -13
- package/src/register-registry-page-route/resolve-request-graph.test.ts +9 -13
- package/src/resolve-from-loader-config.test.ts +10 -14
- package/src/resolve-from-loader-config.ts +28 -115
- package/src/resolve-service-port.test.ts +8 -12
- package/src/resolvers/app-header.test.ts +9 -5
- package/src/resolvers/fetch-org-brand.ts +18 -2
- package/src/resolvers/portal-route-paths.test.ts +8 -13
- package/src/resource-chat-inject.page-assistant.test.ts +8 -28
- package/src/resource-chat-inject.test.ts +8 -25
- package/src/resource-not-found.test.ts +9 -8
- package/src/security-headers.test.ts +9 -12
- package/src/select-service-routes.test.ts +9 -0
- package/src/server-error-page.test.ts +8 -10
- package/src/theme-fonts-link.test.ts +8 -10
- package/src/two-phase-full-app.test.ts +8 -16
- package/src/two-phase-health-gate-client-only.test.ts +8 -28
- package/src/two-phase-health-gate.test.ts +8 -28
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* These tests pin the observable contract: the omission is LOGGED naming the
|
|
11
|
-
* slot, once per slot per process (hot path never spams), and the resolved
|
|
12
|
-
* regions still render every slot that did resolve.
|
|
13
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
9
|
+
|
|
14
10
|
import { describe, expect, mock, test } from "bun:test";
|
|
15
11
|
|
|
16
12
|
const warn = mock((_message: string) => {});
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* route with no page row throws the dead-link floor, and a content route with
|
|
11
|
-
* an EMPTY page row throws the 200 empty-page floor — the three distinct
|
|
12
|
-
* surfaces the page binder maps to three distinct responses.
|
|
13
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
9
|
+
|
|
14
10
|
import { describe, expect, mock, test } from "bun:test";
|
|
15
11
|
|
|
16
12
|
let loaderGraph: Record<string, unknown> = {};
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* reaches the resolver), while a URL param (e.g. :id) still resolves from params.
|
|
10
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
11
9
|
|
|
12
10
|
import { describe, expect, test } from "bun:test";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
resolveFromLoaderConfig,
|
|
16
|
-
} from "./resolve-from-loader-config";
|
|
11
|
+
import { resolveFromLoaderConfig } from "./resolve-from-loader-config";
|
|
12
|
+
import { interpolateMeta } from "./interpolate-meta.ts";
|
|
17
13
|
import {
|
|
18
14
|
getServerClient,
|
|
19
15
|
isServerClientRegistered,
|
|
@@ -16,6 +16,11 @@ import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
|
|
|
16
16
|
import type { ResolverFnKey } from "@teamscala/db/registry/generated/resolver-fn";
|
|
17
17
|
import { ResolverFnConfigSchema } from "@teamscala/db-validation/registry-schemas/resolver-fn";
|
|
18
18
|
import { getServerClient } from "./page-server-orpc/server-client";
|
|
19
|
+
import { interpolateTemplate, resolveParamRef } from "./interpolate-meta";
|
|
20
|
+
import {
|
|
21
|
+
MissingLoaderModelCapabilityError,
|
|
22
|
+
isUrlRefNotUuidError,
|
|
23
|
+
} from "./loader-errors";
|
|
19
24
|
|
|
20
25
|
interface SourceConfig {
|
|
21
26
|
key: string;
|
|
@@ -36,105 +41,6 @@ interface LoaderConfig {
|
|
|
36
41
|
merge_strategy?: Record<string, unknown>;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
class MissingLoaderModelCapabilityError extends Error {
|
|
40
|
-
constructor(model: string, sourceKey: string) {
|
|
41
|
-
super(
|
|
42
|
-
`[resource_loader] source "${sourceKey}" requires model "${model}", but the generated server client does not expose findFirst for it — regenerate the service schema from its page loaders`,
|
|
43
|
-
);
|
|
44
|
-
this.name = "MissingLoaderModelCapabilityError";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function resolveParamRef(
|
|
49
|
-
ref: string,
|
|
50
|
-
params: Record<string, unknown>,
|
|
51
|
-
resolved: Record<string, unknown>,
|
|
52
|
-
): unknown {
|
|
53
|
-
const parts = ref.split(".");
|
|
54
|
-
// A bare (no-dot) ref can name either a resolved source key (e.g. `:response`
|
|
55
|
-
// passes the whole resolved source object to a resolver_fn) or a URL param
|
|
56
|
-
// (e.g. `:id`). Prefer the resolved source key, then fall back to the param.
|
|
57
|
-
// Dotted refs (`:response.total_score`) always read from `resolved`.
|
|
58
|
-
if (parts.length === 1) {
|
|
59
|
-
if (resolved != null && typeof resolved === "object" && ref in resolved) {
|
|
60
|
-
return (resolved as Record<string, unknown>)[ref];
|
|
61
|
-
}
|
|
62
|
-
if (params != null && typeof params === "object" && ref in params) {
|
|
63
|
-
return (params as Record<string, unknown>)[ref];
|
|
64
|
-
}
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
let current: unknown = resolved;
|
|
68
|
-
for (const part of parts) {
|
|
69
|
-
if (current == null || typeof current !== "object") return undefined;
|
|
70
|
-
current = readField(current as Record<string, unknown>, part);
|
|
71
|
-
}
|
|
72
|
-
return current;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* A resource_loader row spells DATABASE COLUMN NAMES — `page_id`,
|
|
77
|
-
* `organisation_id`, `prepared_for` — because that is what the schema calls
|
|
78
|
-
* them. The ORM hands back CAMELCASE keys — `pageId`, `organisationId`,
|
|
79
|
-
* `preparedFor`. Nothing reconciled the two, so a `:proposal.page_id` ref read
|
|
80
|
-
* a key the object does not have and returned undefined.
|
|
81
|
-
*
|
|
82
|
-
* MEASURED 2026-09-03, and the consequence is far worse than a missing value.
|
|
83
|
-
* `interpolateTemplate` DROPS a key whose value is undefined, so a dependent
|
|
84
|
-
* source declared `where: { id: ":proposal.page_id" }` was issued as
|
|
85
|
-
* `where: {}` — and findFirst with an empty filter returns AN ARBITRARY FIRST
|
|
86
|
-
* ROW. A client proposal page therefore loaded an unrelated builda_page,
|
|
87
|
-
* rendered a stranger's content under the correct brand chrome, and answered
|
|
88
|
-
* 200 throughout. Instrumenting the live resolver showed every dependent
|
|
89
|
-
* source doing it: page, website, contact, partnerOrg and clientOrg all with
|
|
90
|
-
* `where={}`; only `proposal` was correct, because its ref is the URL param
|
|
91
|
-
* `:id` rather than a sibling source.
|
|
92
|
-
*
|
|
93
|
-
* Read the spelled key first, then its camelCase equivalent. Column names stay
|
|
94
|
-
* the authoring vocabulary and a row never has to know what the ORM did to its
|
|
95
|
-
* keys.
|
|
96
|
-
*/
|
|
97
|
-
function readField(source: Record<string, unknown>, key: string): unknown {
|
|
98
|
-
if (key in source) return source[key];
|
|
99
|
-
const camel = key.replace(/_([a-z0-9])/g, (_m, c: string) => c.toUpperCase());
|
|
100
|
-
return camel === key ? undefined : source[camel];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function interpolateTemplate(
|
|
104
|
-
template: unknown,
|
|
105
|
-
params: Record<string, unknown>,
|
|
106
|
-
resolved: Record<string, unknown>,
|
|
107
|
-
): unknown {
|
|
108
|
-
if (typeof template === "string") {
|
|
109
|
-
const exactMatch = template.match(/^:([a-zA-Z0-9_.]+)$/);
|
|
110
|
-
if (exactMatch) {
|
|
111
|
-
return resolveParamRef(exactMatch[1], params, resolved);
|
|
112
|
-
}
|
|
113
|
-
if (template.includes(":")) {
|
|
114
|
-
return template.replace(
|
|
115
|
-
/:([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)/g,
|
|
116
|
-
(_match, ref: string) => {
|
|
117
|
-
const value = resolveParamRef(ref, params, resolved);
|
|
118
|
-
return value != null ? String(value) : _match;
|
|
119
|
-
},
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
return template;
|
|
123
|
-
}
|
|
124
|
-
if (Array.isArray(template)) {
|
|
125
|
-
return template.map((v) => interpolateTemplate(v, params, resolved));
|
|
126
|
-
}
|
|
127
|
-
if (template != null && typeof template === "object") {
|
|
128
|
-
const out: Record<string, unknown> = {};
|
|
129
|
-
for (const [k, v] of Object.entries(template)) {
|
|
130
|
-
const result = interpolateTemplate(v, params, resolved);
|
|
131
|
-
if (result !== undefined) out[k] = result;
|
|
132
|
-
}
|
|
133
|
-
return out;
|
|
134
|
-
}
|
|
135
|
-
return template;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
44
|
function toSelectMap(
|
|
139
45
|
fields: string[] | undefined,
|
|
140
46
|
): Record<string, boolean> | undefined {
|
|
@@ -370,10 +276,33 @@ export async function resolveFromLoaderConfig(
|
|
|
370
276
|
if (!source || !result) continue;
|
|
371
277
|
if (result.status === "fulfilled") {
|
|
372
278
|
resolved[source.key] = result.value;
|
|
279
|
+
} else if (isUrlRefNotUuidError(result.reason)) {
|
|
280
|
+
// A URL ref that cannot be a uuid names no resource: resolve the
|
|
281
|
+
// source ABSENT (null) and let the existing semantics decide —
|
|
282
|
+
// the primary-missing check upstream turns it into the branded
|
|
283
|
+
// 404 (PrimaryResourceMissingError, which the render-budget
|
|
284
|
+
// breaker already excludes), an optional source treats it like
|
|
285
|
+
// any absent row. Throwing here instead is what let dead links
|
|
286
|
+
// poison the breaker into a site-wide 503 (see the predicate).
|
|
287
|
+
getAppLogger().warn(
|
|
288
|
+
`[resource_loader] source "${source.key}" got a non-UUID URL ref — treating as MISSING for this request: ${
|
|
289
|
+
result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
290
|
+
}`,
|
|
291
|
+
);
|
|
292
|
+
resolved[source.key] = null;
|
|
373
293
|
} else if (
|
|
374
294
|
source.optional &&
|
|
375
295
|
!(result.reason instanceof MissingLoaderModelCapabilityError)
|
|
376
296
|
) {
|
|
297
|
+
// Optional means MAY BE ABSENT, never FAIL IN SILENCE: the source
|
|
298
|
+
// still resolves to null, but the reason becomes evidence.
|
|
299
|
+
getAppLogger().warn(
|
|
300
|
+
`[resource_loader] optional source "${source.key}" FAILED and is absent from this render: ${
|
|
301
|
+
result.reason instanceof Error
|
|
302
|
+
? `${result.reason.message}\n${result.reason.stack ?? ""}`
|
|
303
|
+
: String(result.reason)
|
|
304
|
+
}`,
|
|
305
|
+
);
|
|
377
306
|
resolved[source.key] = null;
|
|
378
307
|
} else {
|
|
379
308
|
throw result.reason;
|
|
@@ -385,19 +314,3 @@ export async function resolveFromLoaderConfig(
|
|
|
385
314
|
|
|
386
315
|
return resolved;
|
|
387
316
|
}
|
|
388
|
-
|
|
389
|
-
export function interpolateMeta(
|
|
390
|
-
template: Record<string, unknown>,
|
|
391
|
-
data: Record<string, unknown>,
|
|
392
|
-
): Record<string, unknown> {
|
|
393
|
-
const out: Record<string, unknown> = {};
|
|
394
|
-
for (const [k, v] of Object.entries(template)) {
|
|
395
|
-
if (v != null && typeof v === "object" && !Array.isArray(v)) {
|
|
396
|
-
out[k] = interpolateMeta(v as Record<string, unknown>, data);
|
|
397
|
-
} else {
|
|
398
|
-
const result = interpolateTemplate(v, {}, data);
|
|
399
|
-
out[k] = result ?? v;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return out;
|
|
403
|
-
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* port nothing was listening on, and the failure surfaced as broken tool
|
|
10
|
-
* curation rather than as a port problem. It then did it again on 2026-07-31
|
|
11
|
-
* to scala-quiz — see the kind:ui block below.
|
|
12
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
13
9
|
|
|
14
10
|
import { describe, expect, test } from "bun:test";
|
|
15
11
|
import { resolveServicePort } from "./resolve-service-port.ts";
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
9
|
+
|
|
6
10
|
import { describe, expect, test } from "bun:test";
|
|
7
11
|
import { resolveAppHeader } from "./app-header.ts";
|
|
8
12
|
|
|
@@ -12,17 +12,33 @@
|
|
|
12
12
|
* hardcoded `fetchOrgBrand` arm in resolve-from-loader-config.ts.
|
|
13
13
|
*/
|
|
14
14
|
import { fetchOrgBrand } from "@teamscala/resolver-engine/data-resolvers/fetch-org-brand";
|
|
15
|
+
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
15
16
|
import type { ResolverArgs } from "./portal-route-paths.ts";
|
|
16
17
|
|
|
18
|
+
/** A null brand renders a page with no logo, colours or name — so each failure names which of the three it was. */
|
|
17
19
|
export async function fetchOrgBrandWithFallback({ input, client }: ResolverArgs): Promise<unknown> {
|
|
18
20
|
const orgId = input.orgId as string | undefined;
|
|
21
|
+
const fallbackOrgId = input.fallbackOrgId as string | undefined;
|
|
19
22
|
if (orgId) {
|
|
20
23
|
const result = await fetchOrgBrand(client as Parameters<typeof fetchOrgBrand>[0], orgId);
|
|
21
24
|
if (result) return result;
|
|
25
|
+
getAppLogger().warn(
|
|
26
|
+
`[fetch-org-brand] org "${orgId}" resolved NO brand row — the page renders with no logo, colours or company name. Either organisation_profile has no row for that id, or the server client exposes no organisation_profile.findFirst.`,
|
|
27
|
+
);
|
|
28
|
+
} else {
|
|
29
|
+
getAppLogger().warn(
|
|
30
|
+
`[fetch-org-brand] no orgId reached the resolver (input keys: ${Object.keys(input).join(", ") || "none"}) — the resource_loader's resolver_input ref did not interpolate, so the page renders unbranded.`,
|
|
31
|
+
);
|
|
22
32
|
}
|
|
23
|
-
const fallbackOrgId = input.fallbackOrgId as string | undefined;
|
|
24
33
|
if (fallbackOrgId && fallbackOrgId !== orgId) {
|
|
25
|
-
|
|
34
|
+
const fallback = await fetchOrgBrand(
|
|
35
|
+
client as Parameters<typeof fetchOrgBrand>[0],
|
|
36
|
+
fallbackOrgId,
|
|
37
|
+
);
|
|
38
|
+
if (fallback) return fallback;
|
|
39
|
+
getAppLogger().warn(
|
|
40
|
+
`[fetch-org-brand] fallback org "${fallbackOrgId}" also resolved no brand row.`,
|
|
41
|
+
);
|
|
26
42
|
}
|
|
27
43
|
return null;
|
|
28
44
|
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* 1, never segment 0. The fixture pairs below are the regression guard for the
|
|
10
|
-
* two cross-tenant defects this centralises (found 2026-07-21): reading segment 0
|
|
11
|
-
* fed a website id into brand resolution and silently rendered org-less portal
|
|
12
|
-
* URLs unscoped.
|
|
13
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
14
9
|
|
|
15
10
|
import { describe, expect, test } from "bun:test";
|
|
16
11
|
import {
|
|
@@ -1,31 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* THE DEFECT THIS PINS (found 2026-09-08, quiz report page): the page_route's
|
|
10
|
-
* assistant named resource_key "response" — the loader's RAW response hit.
|
|
11
|
-
* On /report/<response-id> that hit exists and the chat bubble injected. On
|
|
12
|
-
* anchor links (a research-record id, live or superseded) the raw hit is
|
|
13
|
-
* null, injectResourceChatLauncher read no organisation_id off it, returned
|
|
14
|
-
* SILENTLY, and the page rendered a complete report at 200 with the
|
|
15
|
-
* assistant simply absent — 552KB of script gone, no error anywhere. The
|
|
16
|
-
* page's reportData was byte-identical across all three shapes, which is
|
|
17
|
-
* exactly why a data-equality check could not see it: the launcher is a
|
|
18
|
-
* separate BLOCK whose presence depends on resolved[resourceKey], not on the
|
|
19
|
-
* report data.
|
|
20
|
-
*
|
|
21
|
-
* THE FIX SHAPE: the page's resource_key names the PAIRED key ("pair"),
|
|
22
|
-
* which exists on every link shape and carries organisation_id (the pair
|
|
23
|
-
* resolver returns the anchor half's organisation). These tests drive the
|
|
24
|
-
* injector with graphs shaped exactly like the loader produces for each of
|
|
25
|
-
* the three link shapes and assert the launcher lands in the rendered
|
|
26
|
-
* content tree — the thing that becomes markup — for ALL THREE. A
|
|
27
|
-
* data-equality assertion cannot pass in their place.
|
|
28
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
29
9
|
|
|
30
10
|
import { describe, expect, mock, test } from "bun:test";
|
|
31
11
|
|
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* silently absent on every gated proposal and agreement. discover-blocks keys
|
|
10
|
-
* the Puck component registry off `block_schema.slug` and nothing else
|
|
11
|
-
* (`components[row.slug] = …`), so a pushed block `type` must EQUAL an active
|
|
12
|
-
* slug. The launcher block was consolidated to the single `ResourceLauncher`
|
|
13
|
-
* row, but the injectors still pushed the pre-consolidation names —
|
|
14
|
-
* `ResourceChatLauncher` here, and `ChatLauncherBlock` in the bespoke proposal
|
|
15
|
-
* injector that has since been deleted in favour of this one. Neither name
|
|
16
|
-
* resolves, and because injection is deliberately fail-soft the page
|
|
17
|
-
* rendered unchanged with no error: the classic shape of
|
|
18
|
-
* `a-deleted-producer-leaves-its-consumers-executing`.
|
|
19
|
-
*
|
|
20
|
-
* Asserting the literal is the WEAK half of the fix — it catches a regression
|
|
21
|
-
* of this exact string, not a future rename of the block. The strong half is
|
|
22
|
-
* single-sourcing the name so it cannot be spelled at all
|
|
23
|
-
* (`identity-is-imported-never-spelled`); until that lands, this test is what
|
|
24
|
-
* stands between the bubble and another silent disappearance.
|
|
25
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
26
9
|
|
|
27
10
|
import { describe, expect, mock, test } from "bun:test";
|
|
28
11
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
9
|
+
|
|
9
10
|
import { describe, expect, it } from "bun:test";
|
|
10
11
|
import {
|
|
11
12
|
PrimaryResourceMissingError,
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Covers both modes: the seeded-registry path (config row applied, CSP
|
|
11
|
-
* {{nonce}} substituted) and the DB-less fallback (secureHeaders defaults).
|
|
12
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
9
|
+
|
|
13
10
|
import { describe, expect, it } from "bun:test";
|
|
14
11
|
import { Hono } from "hono";
|
|
15
12
|
import { injectSecurityHeaders } from "./security-headers.ts";
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
9
|
+
|
|
1
10
|
import { describe, expect, test } from "bun:test";
|
|
2
11
|
import { selectServiceRoutes } from "./select-service-routes.ts";
|
|
3
12
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Retry-After, and the errorId that ties the screen to GlitchTip + the log.
|
|
10
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
11
9
|
|
|
12
10
|
import { describe, expect, test } from "bun:test";
|
|
13
11
|
import type { ErrorReport } from "@teamscala/os/errors/error-report";
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* serif never loaded and fell back to Times (2026-09-02).
|
|
10
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
11
9
|
|
|
12
10
|
import { describe, expect, mock, test } from "bun:test";
|
|
13
11
|
|
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Asserted against the SOURCE rather than by booting a service, because the
|
|
10
|
-
* behaviour is an ordering/error-handling property of buildFullApp and booting
|
|
11
|
-
* it here would need a database, a registry, a Vite build and a live pg-boss.
|
|
12
|
-
* The property is structural: buildClientAssets must be inside a try/catch that
|
|
13
|
-
* appears BEFORE the mountPgBoss call. That is exactly
|
|
14
|
-
* what regressed, and a source assertion fails the moment someone re-awaits it
|
|
15
|
-
* unguarded — which is the regression this locks.
|
|
16
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
17
9
|
|
|
18
10
|
import { describe, expect, test } from "bun:test";
|
|
19
11
|
|
|
@@ -1,31 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Separate file from `two-phase-health-gate.test.ts` on purpose: `fullAppReady`
|
|
10
|
-
* is module state that only transitions false -> true and has no reset hook, so
|
|
11
|
-
* the two ready-paths cannot both be exercised in one module instance. A reset
|
|
12
|
-
* hook would exist only for tests.
|
|
13
|
-
*
|
|
14
|
-
* THE REGRESSION THIS LOCKS (measured 2026-08-07 on scala-agents-ui). Readiness
|
|
15
|
-
* used to be flipped INSIDE `setPageHandler`, which was honest while every
|
|
16
|
-
* service was SSR. `render_mode=client` made a service that completes phase 2
|
|
17
|
-
* and never calls it, so `/health` answered `{"status":"starting"}` FOREVER
|
|
18
|
-
* while the process was healthy and serving — boot completed in 5023ms, "Full
|
|
19
|
-
* app built: 50 block(s)" logged, `GET /` correctly 302'd to sign-in, and three
|
|
20
|
-
* untouched UI services reported ok beside it.
|
|
21
|
-
*
|
|
22
|
-
* That is precisely what `readiness-gated-boot` forbids ("binary: fully-ready or
|
|
23
|
-
* cleanly-unavailable"): a permanently-un-ready health signal makes the
|
|
24
|
-
* activator's wake gate and every health-gated router treat a working service as
|
|
25
|
-
* down. Exercised through a real Hono request against the REAL exported handler
|
|
26
|
-
* (`proxy-is-not-behaviour`) — a source grep would prove the call exists, never
|
|
27
|
-
* that the endpoint answers 200.
|
|
28
|
-
*/
|
|
1
|
+
// ! @system codegen
|
|
2
|
+
// ! @status generated
|
|
3
|
+
// ! @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
+
// !
|
|
5
|
+
// ! This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
+
// ! carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
+
// ! overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
+
// ! with it into the band.
|
|
29
9
|
|
|
30
10
|
import { beforeAll, describe, expect, test } from "bun:test";
|
|
31
11
|
import { Hono } from "hono";
|