@teamscala/service-boot 0.23.372 → 0.23.374
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
CHANGED
|
@@ -65,10 +65,10 @@ describe("decideSweep — thresholds", () => {
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
test("the 490-over-12h shape escalates (duration + recurrence)", () => {
|
|
68
|
-
const sustained = agg("host-command-bus.
|
|
68
|
+
const sustained = agg("host-command-bus.command.failed", 490, 720, 0);
|
|
69
69
|
const decision = decideSweep([sustained], [], POLICY, NOW);
|
|
70
70
|
expect(decision.escalate).toHaveLength(1);
|
|
71
|
-
expect(decision.escalate[0]!.aggregate.kind).toBe("host-command-bus.
|
|
71
|
+
expect(decision.escalate[0]!.aggregate.kind).toBe("host-command-bus.command.failed");
|
|
72
72
|
expect(decision.escalate[0]!.episode).toBeNull();
|
|
73
73
|
});
|
|
74
74
|
|
|
@@ -85,9 +85,9 @@ describe("decideSweep — thresholds", () => {
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test("override raises a floor too — bursty kinds can be tightened", () => {
|
|
88
|
-
const sustained = agg("llm.provider.
|
|
88
|
+
const sustained = agg("llm.provider.call.failed", 40, 60, 0); // passes default
|
|
89
89
|
const decision = decideSweep([sustained], [], POLICY, NOW, {
|
|
90
|
-
"llm.provider.
|
|
90
|
+
"llm.provider.call.failed": { minCount: 100 },
|
|
91
91
|
});
|
|
92
92
|
expect(decision.escalate).toHaveLength(0);
|
|
93
93
|
});
|
|
@@ -95,16 +95,16 @@ describe("decideSweep — thresholds", () => {
|
|
|
95
95
|
|
|
96
96
|
describe("decideSweep — dedup (one escalation per fault, no re-fire)", () => {
|
|
97
97
|
test("already escalated and unresolved does NOT re-escalate", () => {
|
|
98
|
-
const sustained = agg("host-command-bus.
|
|
99
|
-
const open = episode({ kind: "host-command-bus.
|
|
98
|
+
const sustained = agg("host-command-bus.command.failed", 500, 720, 0);
|
|
99
|
+
const open = episode({ kind: "host-command-bus.command.failed", escalatedAt: new Date(NOW.getTime() - 3_600_000) });
|
|
100
100
|
const decision = decideSweep([sustained], [open], POLICY, NOW);
|
|
101
101
|
expect(decision.escalate).toHaveLength(0);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
test("open delivered episode past the update window gets an UPDATE, not a message", () => {
|
|
105
|
-
const sustained = agg("llm.provider.
|
|
105
|
+
const sustained = agg("llm.provider.call.failed", 300, 720, 0);
|
|
106
106
|
const open = episode({
|
|
107
|
-
kind: "llm.provider.
|
|
107
|
+
kind: "llm.provider.call.failed",
|
|
108
108
|
escalatedAt: new Date(NOW.getTime() - 90 * 60_000), // last update long ago
|
|
109
109
|
lastUpdatedAt: new Date(NOW.getTime() - 90 * 60_000),
|
|
110
110
|
});
|
|
@@ -115,9 +115,9 @@ describe("decideSweep — dedup (one escalation per fault, no re-fire)", () => {
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
test("open episode inside the update window gets NOTHING (dedup window)", () => {
|
|
118
|
-
const sustained = agg("llm.provider.
|
|
118
|
+
const sustained = agg("llm.provider.call.failed", 300, 720, 0);
|
|
119
119
|
const open = episode({
|
|
120
|
-
kind: "llm.provider.
|
|
120
|
+
kind: "llm.provider.call.failed",
|
|
121
121
|
escalatedAt: new Date(NOW.getTime() - 10 * 60_000),
|
|
122
122
|
lastUpdatedAt: new Date(NOW.getTime() - 10 * 60_000),
|
|
123
123
|
});
|
|
@@ -150,9 +150,9 @@ describe("decideSweep — resolution + re-arm", () => {
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
test("after resolution the same fault escalates again (new episode)", () => {
|
|
153
|
-
const recurring = agg("host-command-bus.
|
|
153
|
+
const recurring = agg("host-command-bus.command.failed", 50, 40, 0);
|
|
154
154
|
const resolved = episode({
|
|
155
|
-
kind: "host-command-bus.
|
|
155
|
+
kind: "host-command-bus.command.failed",
|
|
156
156
|
resolvedAt: new Date(NOW.getTime() - 35 * 60_000),
|
|
157
157
|
});
|
|
158
158
|
const decision = decideSweep([recurring], [resolved], POLICY, NOW);
|
|
@@ -190,12 +190,12 @@ describe("extractGowaMessageId — delivery verification", () => {
|
|
|
190
190
|
describe("composeEscalationMessage", () => {
|
|
191
191
|
test("carries the three escalation-routing fields and stays phone-sized", () => {
|
|
192
192
|
const message = composeEscalationMessage({
|
|
193
|
-
kind: "host-command-bus.
|
|
194
|
-
aggregate: agg("host-command-bus.
|
|
193
|
+
kind: "host-command-bus.command.failed",
|
|
194
|
+
aggregate: agg("host-command-bus.command.failed", 490, 720, 0),
|
|
195
195
|
impact: "scala-tools verbs are failing host-wide",
|
|
196
196
|
recommendation: "check oxmgr logs for the bus consumer",
|
|
197
197
|
});
|
|
198
|
-
expect(message).toContain("Platform fault: host-command-bus.
|
|
198
|
+
expect(message).toContain("Platform fault: host-command-bus.command.failed");
|
|
199
199
|
expect(message).toContain("Observed: 490 errors over 12 h");
|
|
200
200
|
expect(message).toContain("Impact: scala-tools verbs are failing host-wide");
|
|
201
201
|
expect(message).toContain("Recommendation: check oxmgr logs for the bus consumer");
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The `platform-fault-escalation` pg-boss handler — closes the "receiving is
|
|
7
7
|
* decoupled from consequence" gap measured 2026-08-15 (760 error events over
|
|
8
|
-
* 12 hours, zero escalations: host-command-bus.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* 12 hours, zero escalations: host-command-bus.command.failed (then spelled
|
|
9
|
+
* host-command-bus.error) 490, llm.provider.call.failed (then spelled
|
|
10
|
+
* llm.provider.error) 231, guard.fail.open 16, scale-to-zero.wake_failed 12,
|
|
11
|
+
* listen.connect_failed 11). Emission works and observability-is-passive is satisfied; nothing
|
|
11
12
|
* acted. This sweep makes RECEIVING have CONSEQUENCE: sustained platform
|
|
12
13
|
* faults escalate to the OPERATOR over the existing PA path (WhatsApp, the
|
|
13
14
|
* scala PA operator-relay bot — escalation-routing's channel; no second
|
|
@@ -599,6 +599,73 @@ export async function registerRegistryPageRoute(
|
|
|
599
599
|
// Branding fetch failed — keep the default brand (no regression).
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
|
+
// BRAND FROM THE ORG THAT OWNS THE RESOURCE, not the website's owner.
|
|
603
|
+
// A public resource page (an assessment report, a shared document) is
|
|
604
|
+
// hosted on a shared website but BELONGS to a tenant. resolvePageBrand
|
|
605
|
+
// reads pageData.orgId, which is the WEBSITE's org, so every tenant's
|
|
606
|
+
// report rendered the host org's logo and palette.
|
|
607
|
+
//
|
|
608
|
+
// Measured 2026-08-17: the Launcha Authority Blueprint is owned by GMG
|
|
609
|
+
// APAC (#FF4500, launcha logo) and rendered with GBG Indo's navy and
|
|
610
|
+
// scala mark, because both pages live on the shared "Public Pages"
|
|
611
|
+
// site. The loader had already resolved the right org — nothing
|
|
612
|
+
// consumed it.
|
|
613
|
+
//
|
|
614
|
+
// `brand_org_path` names where the owning org id lives in the resolved
|
|
615
|
+
// loader data (e.g. "quiz.organisation_id"), so which resource owns the
|
|
616
|
+
// brand is a ROW, never a per-service branch
|
|
617
|
+
// (`business-logic-is-rows-plus-executors`). Absent or unresolvable,
|
|
618
|
+
// the website's org stands — this can only ever narrow the brand to a
|
|
619
|
+
// more specific owner, never blank it.
|
|
620
|
+
const brandOrgPath = (routeConfig as { brand_org_path?: string }).brand_org_path;
|
|
621
|
+
if (brandOrgPath) {
|
|
622
|
+
let cursor: unknown = resolved;
|
|
623
|
+
for (const key of brandOrgPath.split(".")) {
|
|
624
|
+
if (cursor == null || typeof cursor !== "object") {
|
|
625
|
+
cursor = undefined;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
cursor = (cursor as Record<string, unknown>)[key];
|
|
629
|
+
}
|
|
630
|
+
if (typeof cursor === "string" && cursor.length > 0) {
|
|
631
|
+
pageData.orgId = cursor;
|
|
632
|
+
// orgId alone is not enough. resolvePageBrand fills only the
|
|
633
|
+
// accent/secondary/muted gaps from the org; primaryColor, logo and
|
|
634
|
+
// companyName come from pageData.brand, which was built from the
|
|
635
|
+
// website's org. Replace those too, the same way the session
|
|
636
|
+
// override above does, or the page keeps the host's logo while
|
|
637
|
+
// claiming the tenant's org.
|
|
638
|
+
try {
|
|
639
|
+
const owner = await fetchOrganisationPageBranding(
|
|
640
|
+
getServerClient() as never,
|
|
641
|
+
cursor,
|
|
642
|
+
);
|
|
643
|
+
if (owner) {
|
|
644
|
+
const existing = (pageData.brand as Record<string, unknown>) ?? {};
|
|
645
|
+
const existingLogo = (existing.logo as Record<string, unknown>) ?? {};
|
|
646
|
+
pageData.brand = {
|
|
647
|
+
...existing,
|
|
648
|
+
companyName: owner.company_name,
|
|
649
|
+
companyFriendlyName: owner.company_name,
|
|
650
|
+
primaryColor: owner.brand_primary_color ?? existing.primaryColor,
|
|
651
|
+
headingFont: owner.brand_heading_font ?? existing.headingFont,
|
|
652
|
+
bodyFont: owner.brand_body_font ?? existing.bodyFont,
|
|
653
|
+
logo: {
|
|
654
|
+
light: owner.company_logo_for_light_theme,
|
|
655
|
+
dark: owner.company_logo_for_dark_theme,
|
|
656
|
+
brand: existingLogo.brand ?? null,
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
} catch {
|
|
661
|
+
// Branding fetch failed — keep the website brand (no regression).
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
log.warn(
|
|
665
|
+
`[scala-install:${serviceSlug}] brand_org_path "${brandOrgPath}" resolved no org id for ${params.path}; keeping the website org brand`,
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
602
669
|
warnUncoveredBlocks(pageData, components, serviceSlug, params.path, log);
|
|
603
670
|
// Fire the AI-chat conversation prefetch NOW (in parallel with the
|
|
604
671
|
// proposal/partner/client post-processors below) so its ~eager cost is
|