@catalyst-cloud/schema 0.1.27 → 0.1.29
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/events/registry.ts +20 -0
- package/src/index.ts +2 -0
- package/src/migrations.generated.ts +9 -0
- package/src/mirror.ts +4 -0
- package/src/telemetry/phase-lifecycle-point.ts +79 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed Drizzle schema = single source of truth for the per-tenant Mirror DO SQLite store (CTC-13 / ADR-0002). Shared by the mirror Worker, the host-sync replica, and the browser OPFS replica.",
|
|
6
6
|
"license": "MIT",
|
package/src/events/registry.ts
CHANGED
|
@@ -1843,6 +1843,11 @@ export const telemetryEventTypes = {
|
|
|
1843
1843
|
"A replica read falling back to a secondary path — an internal routing diagnostic, not a ticket/phase coordination fact.",
|
|
1844
1844
|
},
|
|
1845
1845
|
"catalyst.service.health": { reason: "A periodic service health-check gauge." },
|
|
1846
|
+
"ci.run.completed": {
|
|
1847
|
+
reason:
|
|
1848
|
+
"CTC-1261 M4 — a completed check_suite webhook joined to its ticket at ingest time (head_branch == CTC-NNN by convention), zero extra API calls. Gauge/counter-shaped (catalyst_ci_runs_total{conclusion}): the collapsed count per (ticket, conclusion) is the signal, not any one occurrence's identity.",
|
|
1849
|
+
sampling: { kind: "counted" },
|
|
1850
|
+
},
|
|
1846
1851
|
"cloud-feed.would-dispatch": {
|
|
1847
1852
|
reason:
|
|
1848
1853
|
"A 'would-*' counterfactual dispatch evaluation — the dry-run analogue of phase.dispatch.requested, never an actual dispatch.",
|
|
@@ -1945,6 +1950,11 @@ export const telemetryEventTypes = {
|
|
|
1945
1950
|
reason:
|
|
1946
1951
|
"Phase-agent worker-directory garbage collection. phase.terminal.reap-requested alone is 10,015 lines/mo — a periodic sweep re-requesting cleanup of already-terminal workers, not a discrete coordination fact worth replay.",
|
|
1947
1952
|
},
|
|
1953
|
+
"phase.lifecycle": {
|
|
1954
|
+
reason:
|
|
1955
|
+
"CTC-1261 — one closed vocabulary for every phase-transition observation across the relay ladder (claim, refuse, publish-success, release/setup-failure/reclaim), so a 20-30-runner fleet's phase health is queryable from one AE table by ticket. Each occurrence is individually meaningful (a specific ticket/phase/nonce transition), not a rollup-only gauge — always, never sampled away.",
|
|
1956
|
+
sampling: { kind: "always" },
|
|
1957
|
+
},
|
|
1948
1958
|
"phase.predecessor.reap-complete": {
|
|
1949
1959
|
reason:
|
|
1950
1960
|
"Phase-agent worker-directory garbage collection. phase.terminal.reap-requested alone is 10,015 lines/mo — a periodic sweep re-requesting cleanup of already-terminal workers, not a discrete coordination fact worth replay.",
|
|
@@ -2095,6 +2105,16 @@ export const telemetryEventTypes = {
|
|
|
2095
2105
|
reason:
|
|
2096
2106
|
"A periodic context-usage/size sample logged during an agent session — a gauge, not a discrete session-lifecycle fact (contrast session.started/session.ended, which ARE durable).",
|
|
2097
2107
|
},
|
|
2108
|
+
"work.backlog.sampled": {
|
|
2109
|
+
reason:
|
|
2110
|
+
"CTC-1261 M3 — a per-(team,bucket) count from the 5-minute eligibility sweep (explainEligibleWork), the missing starvation time series (catalyst_work_backlog{team,bucket}). Gauge-shaped: only the collapsed count matters, not any one sweep's identity.",
|
|
2111
|
+
sampling: { kind: "counted" },
|
|
2112
|
+
},
|
|
2113
|
+
"work.oldest-eligible-age.sampled": {
|
|
2114
|
+
reason:
|
|
2115
|
+
"CTC-1261 M3 — a per-team age gauge (catalyst_oldest_eligible_age_seconds) from the same sweep as work.backlog.sampled. Documented approximation: time since the team's dispatch queue was last (re)published (dispatch_queue_meta.published_at), not a true per-ticket time-became-eligible (no such timestamp exists in the schema) — a floor bound, not an exact dwell time.",
|
|
2116
|
+
sampling: { kind: "counted" },
|
|
2117
|
+
},
|
|
2098
2118
|
"worker.memory.sampled": {
|
|
2099
2119
|
reason:
|
|
2100
2120
|
"ADR-0039's own telemetry example ('process samples') applied to a phase worker's own process.",
|
package/src/index.ts
CHANGED
|
@@ -86,6 +86,8 @@ export * from "./events/index.js";
|
|
|
86
86
|
// admission gate over the events registry above, and the pure AE/Iceberg record builders both the
|
|
87
87
|
// mirror Worker and any future out-of-Worker emitter import.
|
|
88
88
|
export * from "./telemetry/point.js";
|
|
89
|
+
// CTC-1261 — the phase.lifecycle family's closed status vocabulary and shared point builder.
|
|
90
|
+
export * from "./telemetry/phase-lifecycle-point.js";
|
|
89
91
|
|
|
90
92
|
/** Every table in the Mirror DO store — pass as `drizzle(storage, { schema: mirrorSchema })`. */
|
|
91
93
|
export const mirrorSchema = {
|
|
@@ -235,6 +235,13 @@ export const MIRROR_MIGRATIONS = {
|
|
|
235
235
|
tag: "0031_ctc851_comment_parent_backfill",
|
|
236
236
|
breakpoints: true,
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
idx: 32,
|
|
240
|
+
version: "6",
|
|
241
|
+
when: 1788057508383,
|
|
242
|
+
tag: "0032_elite_donald_blake",
|
|
243
|
+
breakpoints: true,
|
|
244
|
+
},
|
|
238
245
|
],
|
|
239
246
|
},
|
|
240
247
|
migrations: {
|
|
@@ -299,5 +306,7 @@ export const MIRROR_MIGRATIONS = {
|
|
|
299
306
|
"ALTER TABLE `comments` ADD `parent_type` text;--> statement-breakpoint\nALTER TABLE `comments` ADD `parent_entity_id` text;--> statement-breakpoint\nCREATE INDEX `idx_comments_parent` ON `comments` (`parent_type`,`parent_entity_id`,`removed_at`);",
|
|
300
307
|
"0031_ctc851_comment_parent_backfill":
|
|
301
308
|
"-- CTC-851 — backfill comment parents + quarantine the pre-fix empty-issue_id orphans.\n--\n-- 0030 adds the parent_type/parent_entity_id columns and fixes both WRITE legs (webhook normalizer +\n-- reconcile) so new/re-swept comments carry a real parent, but that alone converges only rows the\n-- reconcile re-sweeps — and it walks OPEN issues, so a non-issue comment (or a comment on a closed\n-- issue) keeps its corrupt shape forever. This backfill is what makes the CORPUS canonical rather\n-- than just the new writes.\n--\n-- ⭐ Deliberately a DATA migration in the SHARED bundle (the 0014 pattern): the DO and every host /\n-- OPFS replica apply the same MIRROR_MIGRATIONS through the same runner, so each side repairs its OWN\n-- rows locally. That converges every replica WITHOUT emitting a change_log delta per row (migrations\n-- bypass appendChange), so no ring-buffer flood (the CTC-279 concern).\n--\n-- (a) Healthy rows (a real issue_id) → parent_type='issue', parent_entity_id=issue_id.\n-- (b) Orphans (issue_id empty-string — the measured shape — or NULL, defensively) cannot be repaired\n-- from row data: the replica comments table has no `raw` column and no parent signal beyond the\n-- comment-reply parent_id. A pure in-bundle DML migration cannot call Linear, so it QUARANTINES\n-- them: parent_type='unresolved', issue_id nulled — MARKED with a stated reason, never deleted\n-- (the AC explicitly permits quarantine-with-reason). CTC-851 measured 54 such rows live.\n--\n-- Idempotent by the `parent_type IS NULL` guard: once a row is stamped it is skipped on replay, so a\n-- crash between a statement succeeding and its tag being recorded (execReplaySafe) is safe.\nUPDATE `comments`\n SET `parent_type` = 'issue', `parent_entity_id` = `issue_id`\n WHERE `parent_type` IS NULL AND `issue_id` IS NOT NULL AND `issue_id` <> '';\n--> statement-breakpoint\nUPDATE `comments`\n SET `parent_type` = 'unresolved', `issue_id` = NULL\n WHERE `parent_type` IS NULL AND (`issue_id` = '' OR `issue_id` IS NULL);\n",
|
|
309
|
+
"0032_elite_donald_blake":
|
|
310
|
+
"ALTER TABLE `issues` ADD `creator_id` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `creator_name` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `creator_avatar_url` text;",
|
|
302
311
|
},
|
|
303
312
|
} as const;
|
package/src/mirror.ts
CHANGED
|
@@ -62,6 +62,10 @@ export const issues = sqliteTable(
|
|
|
62
62
|
bot_actor_name: text("bot_actor_name"),
|
|
63
63
|
bot_actor_type: text("bot_actor_type"),
|
|
64
64
|
bot_actor_sub_type: text("bot_actor_sub_type"),
|
|
65
|
+
// CTC-867: the raiser — the Linear issue creator, inline triplet copying comments.author_*.
|
|
66
|
+
creator_id: text("creator_id"),
|
|
67
|
+
creator_name: text("creator_name"),
|
|
68
|
+
creator_avatar_url: text("creator_avatar_url"),
|
|
65
69
|
parent_id: text("parent_id"),
|
|
66
70
|
parent_identifier: text("parent_identifier"),
|
|
67
71
|
// Ticket field enrichment (CTC-TICKET-FIELDS): lifecycle timestamps + due date + snooze + url +
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// phase-lifecycle-point.ts — CTC-1261. The closed `status` vocabulary for the `phase.lifecycle`
|
|
2
|
+
// telemetry family and the shared TelemetryPoint builder every mirror-side emit site (E1/E2/E4)
|
|
3
|
+
// uses, so the four call sites don't hand-build the object literal four different ways.
|
|
4
|
+
//
|
|
5
|
+
// The registry (../events/registry.ts) only runtime-validates the event NAME (assertEmittable) —
|
|
6
|
+
// there is no per-payload-field runtime enum validator anywhere in this package. `status` is a
|
|
7
|
+
// second, independent closed vocabulary layered on top of one registry name, so it needs its own
|
|
8
|
+
// guard here, in the same throw-not-a-falsy-sentinel posture as assertEmittable.
|
|
9
|
+
|
|
10
|
+
import type { TelemetryPoint } from "./point.js";
|
|
11
|
+
|
|
12
|
+
export const PHASE_LIFECYCLE_STATUSES = [
|
|
13
|
+
"claimed",
|
|
14
|
+
"started",
|
|
15
|
+
"succeeded",
|
|
16
|
+
"failed",
|
|
17
|
+
"refused",
|
|
18
|
+
"released",
|
|
19
|
+
"setup_failed",
|
|
20
|
+
"reclaimed",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export type PhaseLifecycleStatus = (typeof PHASE_LIFECYCLE_STATUSES)[number];
|
|
24
|
+
|
|
25
|
+
function assertKnownStatus(status: string): asserts status is PhaseLifecycleStatus {
|
|
26
|
+
if (!(PHASE_LIFECYCLE_STATUSES as readonly string[]).includes(status)) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`phase.lifecycle refuses status "${status}": not one of ${PHASE_LIFECYCLE_STATUSES.join("|")} ` +
|
|
29
|
+
`(packages/schema/src/telemetry/phase-lifecycle-point.ts is the one closed vocabulary)`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PhaseLifecycleFields {
|
|
35
|
+
status: PhaseLifecycleStatus;
|
|
36
|
+
ticket: string;
|
|
37
|
+
phase: string;
|
|
38
|
+
nonce: number;
|
|
39
|
+
executor: string;
|
|
40
|
+
substrate?: string;
|
|
41
|
+
provider?: string;
|
|
42
|
+
branch?: string;
|
|
43
|
+
baseSha?: string;
|
|
44
|
+
prNumber?: number;
|
|
45
|
+
artifactKey?: string;
|
|
46
|
+
durationMs?: number;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build one `phase.lifecycle` TelemetryPoint. Throws (never a falsy sentinel) on an unrecognized
|
|
52
|
+
* `status` — the same posture as `assertEmittable` for the event name.
|
|
53
|
+
*/
|
|
54
|
+
export function buildPhaseLifecyclePoint(fields: PhaseLifecycleFields): TelemetryPoint {
|
|
55
|
+
assertKnownStatus(fields.status);
|
|
56
|
+
const detailParts = [
|
|
57
|
+
`status=${fields.status}`,
|
|
58
|
+
`ticket=${fields.ticket}`,
|
|
59
|
+
`phase=${fields.phase}`,
|
|
60
|
+
`nonce=${fields.nonce}`,
|
|
61
|
+
`executor=${fields.executor}`,
|
|
62
|
+
...(fields.substrate !== undefined ? [`substrate=${fields.substrate}`] : []),
|
|
63
|
+
...(fields.provider !== undefined ? [`provider=${fields.provider}`] : []),
|
|
64
|
+
...(fields.branch !== undefined ? [`branch=${fields.branch}`] : []),
|
|
65
|
+
...(fields.baseSha !== undefined ? [`base_sha=${fields.baseSha}`] : []),
|
|
66
|
+
...(fields.prNumber !== undefined ? [`pr_number=${fields.prNumber}`] : []),
|
|
67
|
+
...(fields.artifactKey !== undefined ? [`artifact_key=${fields.artifactKey}`] : []),
|
|
68
|
+
...(fields.reason !== undefined ? [`reason=${fields.reason}`] : []),
|
|
69
|
+
];
|
|
70
|
+
return {
|
|
71
|
+
name: "phase.lifecycle",
|
|
72
|
+
service: "catalyst-cloud.mirror",
|
|
73
|
+
outcome: fields.status === "failed" || fields.status === "setup_failed" ? "error" : "ok",
|
|
74
|
+
detail: detailParts.join(" "),
|
|
75
|
+
traceId: `${fields.ticket}:${fields.phase}:${fields.nonce}`,
|
|
76
|
+
durationMs: fields.durationMs ?? null,
|
|
77
|
+
count: 1,
|
|
78
|
+
};
|
|
79
|
+
}
|