@volter/twin-sentry 1.0.0 → 1.0.1
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/README.md +3 -2
- package/api/spec/SOURCE.md +19 -2
- package/api/spec/openapi.json.gz +0 -0
- package/api/src/generated/ui.gen.json +1 -1
- package/api/src/manifest.ts +4 -3
- package/api/src/semantics/api.ts +3 -2
- package/api/src/semantics/doors.ts +45 -19
- package/api/src/semantics/shared.ts +1 -5
- package/dist/api/spec/SOURCE.md +19 -2
- package/dist/api/spec/openapi.json.gz +0 -0
- package/dist/api/src/generated/ui.gen.json +1 -1
- package/dist/api/src/manifest.js +4 -3
- package/dist/api/src/manifest.ts +4 -3
- package/dist/api/src/semantics/api.js +4 -2
- package/dist/api/src/semantics/api.ts +3 -2
- package/dist/api/src/semantics/doors.d.ts +1 -4
- package/dist/api/src/semantics/doors.js +51 -19
- package/dist/api/src/semantics/doors.ts +45 -19
- package/dist/api/src/semantics/shared.d.ts +0 -1
- package/dist/api/src/semantics/shared.js +1 -2
- package/dist/api/src/semantics/shared.ts +1 -5
- package/dist/ingest/src/manifest.js +3 -0
- package/dist/ingest/src/manifest.ts +3 -0
- package/dist/journeys/customer-life.json +11 -13
- package/dist/journeys/customer-life.outline.md +5 -5
- package/dist/journeys/demand.json +1 -45
- package/dist/journeys/first-use.json +22 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/manifest.js +3 -2
- package/dist/src/semantics/shared.d.ts +5 -4
- package/dist/src/semantics/shared.js +22 -26
- package/generated/pack-facts.json +14 -0
- package/ingest/src/manifest.ts +3 -0
- package/journeys/customer-life.json +11 -13
- package/journeys/customer-life.outline.md +5 -5
- package/journeys/demand.json +1 -45
- package/journeys/first-use.json +22 -0
- package/package.json +10 -9
- package/src/cli.ts +1 -1
- package/src/index.ts +2 -1
- package/src/manifest.ts +3 -2
- package/src/semantics/shared.ts +22 -26
- package/dist/generated/pack-facts.json +0 -87
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bearerToken, grants, orgBySlug, projectOf
|
|
1
|
+
import { bearerToken, grants, orgBySlug, projectOf } from "../../../src/semantics/shared.js";
|
|
2
2
|
export * from "../../../src/semantics/shared.js";
|
|
3
3
|
export const detail = (status, message) => Response.json({ detail: message }, { status });
|
|
4
4
|
// Where the documentation stops: the spec names these answers' statuses (401, 403, 404) without their bodies; the
|
|
@@ -24,4 +24,3 @@ export function projectFor(ctx, scope) {
|
|
|
24
24
|
const project = projectOf(ctx, org, String(ctx.call.params.project_id_or_slug ?? ''));
|
|
25
25
|
return project ? { org, project } : notFound();
|
|
26
26
|
}
|
|
27
|
-
export function projectView(ctx, p, _org) { return wire(ctx, p); }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// What every family of Sentry's API shares: who is calling (an auth token the World issued), the organization a request
|
|
2
2
|
// names and whether the token may act on it, and the API's answers (Django REST framework's `{"detail": ...}`).
|
|
3
3
|
import type { HandlerContext } from '@volter/world-core';
|
|
4
|
-
import { bearerToken, grants, orgBySlug, projectOf, type Row
|
|
4
|
+
import { bearerToken, grants, orgBySlug, projectOf, type Row } from '../../../src/semantics/shared.ts';
|
|
5
5
|
|
|
6
6
|
export * from '../../../src/semantics/shared.ts';
|
|
7
7
|
|
|
@@ -29,7 +29,3 @@ export function projectFor(ctx: HandlerContext, scope: string): { org: Row; proj
|
|
|
29
29
|
const project = projectOf(ctx, org, String(ctx.call.params.project_id_or_slug ?? ''));
|
|
30
30
|
return project ? { org, project } : notFound();
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export function projectView(ctx: HandlerContext, p: Row, _org: Row): Row { return wire(ctx, p); }
|
|
@@ -2,6 +2,9 @@ export const manifest = {
|
|
|
2
2
|
vendor: 'sentry',
|
|
3
3
|
// one service with the API lane (../../api/src/manifest.ts), so one store
|
|
4
4
|
service: 'sentry',
|
|
5
|
+
hosts: [{ suffix: '.ingest.sentry.io' }, { suffix: '.ingest.us.sentry.io' }, { suffix: '.ingest.de.sentry.io' }],
|
|
6
|
+
// source: spec:/servers/0/url "https://o0.ingest.us.sentry.io"
|
|
7
|
+
remoteHost: { template: 'o0.ingest.us.sentry.io' },
|
|
5
8
|
// an envelope is not JSON: its bytes are kept (./semantics/around.ts) and read by the handler
|
|
6
9
|
body: {},
|
|
7
10
|
// source: https://raw.githubusercontent.com/getsentry/sentry-docs/3ec4e139711af82b38a3c2c17261f5f5cdb66dfc/develop-docs/sdk/foundations/transport/compression.mdx "The following content encodings are accepted by Relay and Sentry:"
|
|
@@ -7,6 +7,9 @@ export const manifest: DerivedManifest = {
|
|
|
7
7
|
vendor: 'sentry',
|
|
8
8
|
// one service with the API lane (../../api/src/manifest.ts), so one store
|
|
9
9
|
service: 'sentry',
|
|
10
|
+
hosts: [{ suffix: '.ingest.sentry.io' }, { suffix: '.ingest.us.sentry.io' }, { suffix: '.ingest.de.sentry.io' }],
|
|
11
|
+
// source: spec:/servers/0/url "https://o0.ingest.us.sentry.io"
|
|
12
|
+
remoteHost: { template: 'o0.ingest.us.sentry.io' },
|
|
10
13
|
// an envelope is not JSON: its bytes are kept (./semantics/around.ts) and read by the handler
|
|
11
14
|
body: {},
|
|
12
15
|
// source: https://raw.githubusercontent.com/getsentry/sentry-docs/3ec4e139711af82b38a3c2c17261f5f5cdb66dfc/develop-docs/sdk/foundations/transport/compression.mdx "The following content encodings are accepted by Relay and Sentry:"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"vendor": "sentry",
|
|
3
3
|
"author": "Contributor 93",
|
|
4
|
-
"about": "Harbourline runs Rallly and Twenty, uses
|
|
4
|
+
"about": "Harbourline runs Rallly and Twenty, uses Scheduler for social channels and Cal.com for booking. Account setup uses the API after bootstrap; releases, source maps and ingestion follow the pinned application clients.",
|
|
5
5
|
"steps": [
|
|
6
6
|
{
|
|
7
7
|
"name": "Monday 5 January, 09:00: Harbourline puts Sentry on Rallly and Twenty",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"evidence": "spec:listProjectKeys"
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
|
-
"name": "Priya creates the
|
|
108
|
+
"name": "Priya creates the scheduler project through Sentry API",
|
|
109
109
|
"method": "POST",
|
|
110
110
|
"path": "https://sentry.io/api/0/organizations/harbourline/projects/",
|
|
111
111
|
"status": 201,
|
|
@@ -113,8 +113,8 @@
|
|
|
113
113
|
"authorization": "Bearer {ci}"
|
|
114
114
|
},
|
|
115
115
|
"body": {
|
|
116
|
-
"name": "
|
|
117
|
-
"slug": "
|
|
116
|
+
"name": "scheduler",
|
|
117
|
+
"slug": "scheduler",
|
|
118
118
|
"platform": "javascript-nextjs"
|
|
119
119
|
},
|
|
120
120
|
"capture": {
|
|
@@ -123,11 +123,11 @@
|
|
|
123
123
|
"expect": [
|
|
124
124
|
{
|
|
125
125
|
"path": "slug",
|
|
126
|
-
"equals": "
|
|
126
|
+
"equals": "scheduler"
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
129
|
"path": "name",
|
|
130
|
-
"equals": "
|
|
130
|
+
"equals": "scheduler"
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
"path": "platform",
|
|
@@ -137,9 +137,9 @@
|
|
|
137
137
|
"evidence": "spec:createOrganizationProject"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
|
-
"name": "She reads the
|
|
140
|
+
"name": "She reads the scheduler DSN from its client key",
|
|
141
141
|
"method": "GET",
|
|
142
|
-
"path": "https://sentry.io/api/0/projects/harbourline/
|
|
142
|
+
"path": "https://sentry.io/api/0/projects/harbourline/scheduler/keys/",
|
|
143
143
|
"status": 200,
|
|
144
144
|
"headers": {
|
|
145
145
|
"authorization": "Bearer {ci}"
|
|
@@ -485,7 +485,7 @@
|
|
|
485
485
|
"client": "@sentry-internal/replay@10.38.0:build/npm/esm/index.js:8039"
|
|
486
486
|
},
|
|
487
487
|
{
|
|
488
|
-
"name": "A
|
|
488
|
+
"name": "A Scheduler user sends feedback from its configured widget",
|
|
489
489
|
"method": "POST",
|
|
490
490
|
"path": "https://{r_host}/api/{p_proj}/envelope/",
|
|
491
491
|
"status": 200,
|
|
@@ -493,18 +493,17 @@
|
|
|
493
493
|
"x-sentry-auth": "Sentry sentry_version=7, sentry_client=sentry.javascript.nextjs/10.56.0, sentry_key={p_key}",
|
|
494
494
|
"content-type": "application/x-sentry-envelope"
|
|
495
495
|
},
|
|
496
|
-
"body": "{\"event_id\":\"fb0000000000400080000000000000a1\",\"sent_at\":\"2026-01-06T14:08:00.000Z\",\"sdk\":{\"name\":\"sentry.javascript.nextjs\",\"version\":\"10.56.0\"}}\n{\"type\":\"feedback\"}\n{\"event_id\":\"fb0000000000400080000000000000a1\",\"timestamp\":\"2026-01-06T14:08:00.000Z\",\"level\":\"info\",\"platform\":\"javascript\",\"contexts\":{\"feedback\":{\"message\":\"The scheduling button is hard to find.\",\"contact_email\":\"u_1182@harbourline.test\",\"name\":\"Dana Ito\",\"url\":\"https://
|
|
496
|
+
"body": "{\"event_id\":\"fb0000000000400080000000000000a1\",\"sent_at\":\"2026-01-06T14:08:00.000Z\",\"sdk\":{\"name\":\"sentry.javascript.nextjs\",\"version\":\"10.56.0\"}}\n{\"type\":\"feedback\"}\n{\"event_id\":\"fb0000000000400080000000000000a1\",\"timestamp\":\"2026-01-06T14:08:00.000Z\",\"level\":\"info\",\"platform\":\"javascript\",\"contexts\":{\"feedback\":{\"message\":\"The scheduling button is hard to find.\",\"contact_email\":\"u_1182@harbourline.test\",\"name\":\"Dana Ito\",\"url\":\"https://scheduler.harbourline.test/launches\"}},\"user\":{\"id\":\"u_1182\",\"email\":\"u_1182@harbourline.test\",\"username\":\"Dana Ito\"},\"sdk\":{\"name\":\"sentry.javascript.nextjs\",\"version\":\"10.56.0\"},\"type\":\"feedback\"}\n",
|
|
497
497
|
"expect": [
|
|
498
498
|
{
|
|
499
499
|
"path": "id",
|
|
500
500
|
"equals": "fb0000000000400080000000000000a1"
|
|
501
501
|
}
|
|
502
502
|
],
|
|
503
|
-
"evidence": "gitroomhq/postiz-app@e0a08a7d9592:apps/frontend/src/components/new-layout/sentry.feedback.component.tsx:24",
|
|
504
503
|
"client": "@sentry/core@10.56.0:build/esm/feedback.js:6"
|
|
505
504
|
},
|
|
506
505
|
{
|
|
507
|
-
"name": "Harbourline schedules a
|
|
506
|
+
"name": "Harbourline schedules a Scheduler post and the service counts post_created",
|
|
508
507
|
"method": "POST",
|
|
509
508
|
"path": "https://{r_host}/api/{p_proj}/envelope/",
|
|
510
509
|
"status": 200,
|
|
@@ -513,7 +512,6 @@
|
|
|
513
512
|
"content-type": "application/x-sentry-envelope"
|
|
514
513
|
},
|
|
515
514
|
"body": "{\"sent_at\":\"2026-01-06T14:30:00.000Z\",\"sdk\":{\"name\":\"sentry.javascript.nestjs\",\"version\":\"10.56.0\"}}\n{\"type\":\"trace_metric\",\"item_count\":1,\"content_type\":\"application/vnd.sentry.items.trace-metric+json\"}\n{\"items\":[{\"timestamp\":1767709800,\"trace_id\":\"abcdef0123456789abcdef0123456789\",\"name\":\"post_created\",\"type\":\"counter\",\"unit\":\"none\",\"value\":1,\"attributes\":{\"sentry.environment\":{\"value\":\"production\",\"type\":\"string\"},\"service\":{\"value\":\"backend\",\"type\":\"string\"}}}],\"version\":2}\n",
|
|
516
|
-
"evidence": "gitroomhq/postiz-app@e0a08a7d9592:libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts:994",
|
|
517
515
|
"client": "@sentry/core@10.56.0:build/esm/metrics/envelope.js:12"
|
|
518
516
|
},
|
|
519
517
|
{
|
|
@@ -5,7 +5,7 @@ Author: Contributor 93
|
|
|
5
5
|
## The customer
|
|
6
6
|
|
|
7
7
|
**Harbourline** runs Rallly (lukevella/rallly at `3e63239dbdec`) and Twenty (twentyhq/twenty at `1d561b8c73c9`) for its team,
|
|
8
|
-
and
|
|
8
|
+
and Scheduler for its social channels, each reporting to Sentry (../journeys/demand.json):
|
|
9
9
|
|
|
10
10
|
- **Rallly**: `@sentry/nextjs` 10.38 on the server, edge and browser (errors, transactions, logs, Session Replay; the
|
|
11
11
|
browser envelopes directly to its DSN host (tunnelRoute is commented out)), and at build `withSentryConfig`, whose sentry-cli makes the release,
|
|
@@ -13,7 +13,7 @@ and Postiz (gitroomhq/postiz-app at `e0a08a7d9592`) for its social channels, eac
|
|
|
13
13
|
- **Twenty**: `@sentry/nestjs` 10.73 on the server (errors, transactions, profiles), its cron jobs checked in as
|
|
14
14
|
monitors (`captureCheckIn` with the schedule).
|
|
15
15
|
|
|
16
|
-
Harbourline also runs Cal.com (calcom/cal.diy at `54343aa685ae`) for bookings. Its release script reads SENTRY_AUTH_TOKEN, makes its git HEAD release, associates local commits, injects source-map debug ids, uploads the bundle and finalizes.
|
|
16
|
+
Harbourline also runs Cal.com (calcom/cal.diy at `54343aa685ae`) for bookings. Its release script reads SENTRY_AUTH_TOKEN, makes its git HEAD release, associates local commits, injects source-map debug ids, uploads the bundle and finalizes. Scheduler pins Sentry 10.56.0.
|
|
17
17
|
|
|
18
18
|
The World clock starts 2026-01-01.
|
|
19
19
|
|
|
@@ -23,12 +23,12 @@ The World clock starts 2026-01-01.
|
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| Priya Raman, Harbourline's engineer | the harbourline organization | bootstrap sign-up; additional projects and keys through the API; token issuance door |
|
|
25
25
|
| Rallly and Cal builds | the bootstrap token and Cal CI token | sentry-cli 2.58.4 |
|
|
26
|
-
| Rallly, Twenty and
|
|
26
|
+
| Rallly, Twenty and Scheduler in production | their projects' DSNs | the SDKs' envelopes |
|
|
27
27
|
|
|
28
28
|
## The arc
|
|
29
29
|
|
|
30
30
|
### 1. Setting up (Mon 5 Jan)
|
|
31
|
-
The bootstrap makes the organization, first auth token and rallly project/DSN. Priya issues Cal's CI auth token for SENTRY_AUTH_TOKEN. With her bootstrap token she creates twenty and
|
|
31
|
+
The bootstrap makes the organization, first auth token and rallly project/DSN. Priya issues Cal's CI auth token for SENTRY_AUTH_TOKEN. With her bootstrap token she creates twenty and scheduler through createOrganizationProject, and reads their DSNs through listProjectKeys. She creates cal for the booking app.
|
|
32
32
|
|
|
33
33
|
### 2. Rallly's release (Tue 6 Jan, 10:00)
|
|
34
34
|
sentry-cli makes `rallly@4.2.0` (a retry answers 208), reads the chunk
|
|
@@ -38,7 +38,7 @@ finalizes the release.
|
|
|
38
38
|
### 3. Production (Tue 6 Jan, 14:30)
|
|
39
39
|
A browser session; an unhandled rejection with its stack trace, and the same error for another visitor in a gzipped
|
|
40
40
|
envelope (one issue, two users); a transaction; a replay of the visitor who hit
|
|
41
|
-
the error; a
|
|
41
|
+
the error; a Scheduler user sends feedback from its configured widget; Scheduler's post_created metric; the server's logs and minute of HTTP request-session aggregates; a client report for sampled-out transactions.
|
|
42
42
|
|
|
43
43
|
### 4. Twenty's cron (Wed 7 Jan, 03:00)
|
|
44
44
|
The cleanup job checks in in progress with its schedule (the monitor is made), then ok using the same check-in id and no explicit duration (checkin_margin 1, max_runtime 5); a worker
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"measured": "Application calls and optional settings are recorded below at the cited commits.",
|
|
3
|
-
"applications": [
|
|
4
|
-
{
|
|
5
|
-
"name": "gitroomhq/postiz-app",
|
|
6
|
-
"commit": "e0a08a7d9592fdc1ef1e82cb7cb781489fceade3",
|
|
7
|
-
"client": "@sentry/nextjs 10.56.0 feedbackIntegration; the SDK envelope transport",
|
|
8
|
-
"frontend": [
|
|
9
|
-
{
|
|
10
|
-
"call": "A Postiz user sends feedback through the shipped feedback button; its message, contact and user context are sent as a feedback envelope item",
|
|
11
|
-
"operation": "sendEnvelope",
|
|
12
|
-
"evidence": "apps/frontend/src/components/new-layout/sentry.feedback.component.tsx:13-24,30-38; libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts:34-38"
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
|
-
"gate": "The shipped button attaches the SDK feedback widget when its DSN is supplied; there is no separate install or feature/provider selection (apps/frontend/src/components/new-layout/sentry.feedback.component.tsx:13-24,30-38; libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts:34-38)."
|
|
16
|
-
}
|
|
17
|
-
],
|
|
3
|
+
"applications": [],
|
|
18
4
|
"notDemanded": "every other operation of Sentry's API (dashboards, alerts, detectors, members, teams, integrations, issue changes, the organization's events and discover queries, replays' and profiles' reads, release files, deploys): no application here calls them.",
|
|
19
5
|
"optional": [
|
|
20
6
|
{
|
|
@@ -37,36 +23,6 @@
|
|
|
37
23
|
"setting": "Configuration: operator instrumentation is a setting that turns the reporting provider on; operator error reporting uses SENTRY_DSN/NEXT_PUBLIC_SENTRY_DSN; no person uses the reporting flow (apps/web/instrumentation-client.ts:9-12).",
|
|
38
24
|
"evidence": "apps/web/instrumentation-client.ts:9-12"
|
|
39
25
|
},
|
|
40
|
-
{
|
|
41
|
-
"commit": "e0a08a7d9592fdc1ef1e82cb7cb781489fceade3",
|
|
42
|
-
"client": "@sentry/nestjs, @sentry/nextjs and @sentry/react 10.56.0 with profiling (bundler plugin 5.3, sentry-cli 2.58.5)",
|
|
43
|
-
"backend": [
|
|
44
|
-
{
|
|
45
|
-
"call": "errors, transactions and profiles from the backend, with its logs",
|
|
46
|
-
"operation": "sendEnvelope",
|
|
47
|
-
"evidence": "libraries/nestjs-libraries/src/sentry/initialize.sentry.ts:34"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"call": "the frontend: Session Replay and browser profiling (user feedback is a separate demanded frontend call)",
|
|
51
|
-
"operation": "sendEnvelope",
|
|
52
|
-
"evidence": "libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts:34"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"call": "metrics: counters, gauges and distributions (Sentry.metrics)",
|
|
56
|
-
"operation": "sendEnvelope",
|
|
57
|
-
"evidence": "libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts:994"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"call": "withSentryConfig at build: release, source maps, finalize",
|
|
61
|
-
"operation": "createProjectRelease; getChunkUploadOptions; uploadChunks; assembleArtifactBundle; updateProjectRelease",
|
|
62
|
-
"evidence": "apps/frontend/next.config.js:60"
|
|
63
|
-
}
|
|
64
|
-
],
|
|
65
|
-
"application": "gitroomhq/postiz-app",
|
|
66
|
-
"what": "@sentry/nestjs, @sentry/nextjs and @sentry/react 10.56.0 with profiling (bundler plugin 5.3, sentry-cli 2.58.5)",
|
|
67
|
-
"setting": "Configuration: operator instrumentation is a setting that turns the reporting provider on; operator error, replay, profile and metrics reporting uses NEXT_PUBLIC_SENTRY_DSN (libraries/react-shared-libraries/src/sentry/initialize.sentry.next.basic.ts:4-7). The shipped feedback button using the same SDK is separately demand; supplying its DSN is a credential, not a provider-selection setting.",
|
|
68
|
-
"evidence": "docker-compose.yaml:89; (apps/frontend/src/instrumentation.ts:2"
|
|
69
|
-
},
|
|
70
26
|
{
|
|
71
27
|
"commit": "1d561b8c73c984058a5121ec229f4805a2424a91",
|
|
72
28
|
"client": "@sentry/nestjs and @sentry/node 10.73 with profiling, @sentry/react 10.59.0 (lockfile resolution of ^10.51.0)",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"source": "https://docs.sentry.io/platforms/javascript/guides/node/usage/",
|
|
4
|
+
"about": "A Node application uses Twenty's demanded @sentry/node 10.73.0 to capture and flush a scoped exception using its issued SENTRY_DSN. The documented project-event API reads the resulting event with the issued SENTRY_AUTH_TOKEN. Resume reads the same event ID and asserts the exception, user and application tag without submitting another envelope.",
|
|
5
|
+
"files": {
|
|
6
|
+
"package.json": "{\n \"name\": \"sentry-exception-customer\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@sentry/node\": \"10.73.0\"\n }\n}\n",
|
|
7
|
+
".env.example": "SENTRY_DSN=\nSENTRY_AUTH_TOKEN=\nSENTRY_ORG=\nSENTRY_PROJECT=\n",
|
|
8
|
+
".gitignore": ".sentry-event.json\n",
|
|
9
|
+
"capture.mjs": "import assert from 'node:assert/strict';\nimport { writeFileSync } from 'node:fs';\nimport * as Sentry from '@sentry/node';\nif (!process.env.VOLTER_WORLD) throw new Error('Run this application through its World.');\nassert.ok(process.env.SENTRY_DSN, 'the application receives its own project DSN');\nSentry.init({ dsn: process.env.SENTRY_DSN, defaultIntegrations: false });\nSentry.setUser({ id: 'customer-member' });\nSentry.setTag('application', 'customer-workflow');\nconst eventId = Sentry.captureException(new Error('Customer workflow exception'));\nassert.match(eventId, /^[a-f0-9]{32}$/i);\nassert.equal(await Sentry.close(5000), true, 'the SDK drains its exception envelope');\nwriteFileSync('.sentry-event.json', JSON.stringify({ eventId }) + '\\n');\nawait import('./readback.mjs');\n",
|
|
10
|
+
"readback.mjs": "import assert from 'node:assert/strict';\nimport { readFileSync } from 'node:fs';\nif (!process.env.VOLTER_WORLD) throw new Error('Run this application through its World.');\nconst { eventId } = JSON.parse(readFileSync('.sentry-event.json', 'utf8'));\n// https://docs.sentry.io/api/events/retrieve-an-event-for-a-project/\nconst url = 'https://sentry.io/api/0/projects/' + encodeURIComponent(process.env.SENTRY_ORG) + '/' + encodeURIComponent(process.env.SENTRY_PROJECT) + '/events/' + eventId + '/';\nconst response = await fetch(url, { headers: { authorization: 'Bearer ' + process.env.SENTRY_AUTH_TOKEN } });\nassert.equal(response.status, 200, 'the issued organization token reads its project event');\nconst event = await response.json();\nassert.equal(event.eventID, eventId);\nassert.equal(event.user.id, 'customer-member');\nassert.ok(event.tags.some(tag => tag.key === 'application' && tag.value === 'customer-workflow'));\nconst exception = event.entries.find(entry => entry.type === 'exception');\nassert.equal(exception.data.values[0].value, 'Customer workflow exception');\nassert.ok(exception.data.values[0].stacktrace.frames.length > 0, 'the SDK stack survives ingestion and resume');\nconsole.log(JSON.stringify({ eventId: event.eventID, exception: exception.data.values[0].value, retained: true }));\n"
|
|
11
|
+
},
|
|
12
|
+
"run": [
|
|
13
|
+
"node",
|
|
14
|
+
"capture.mjs"
|
|
15
|
+
],
|
|
16
|
+
"retention": {
|
|
17
|
+
"readback": [
|
|
18
|
+
"node",
|
|
19
|
+
"readback.mjs"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/src/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ const [cmd, ...rest] = process.argv.slice(2);
|
|
|
7
7
|
const port = Number(optionValue(rest, '--port', '0')) || undefined;
|
|
8
8
|
const root = optionValue(rest, '--root') || undefined;
|
|
9
9
|
const readOnly = hasFlag(rest, '--read-only');
|
|
10
|
-
// the
|
|
10
|
+
// the selected twin's private Postgres, which the runtime binds a pack declaring managedDatabase to
|
|
11
11
|
const database = optionValue(rest, '--database') || undefined;
|
|
12
12
|
// the World's scenario file, for a model vendor's turns
|
|
13
13
|
const scenarioPath = optionValue(rest, '--scenario') || undefined;
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @volter/twin-sentry — the sentry twin (Protocol 3). A fixed file (create-pack): the descriptor is the manifest's, and the
|
|
2
|
-
// kernel derives the vendor-backed half from the generated surface
|
|
2
|
+
// kernel derives the vendor-backed half from the generated surface, unless the manifest declares none (vendorBacked.none;
|
|
3
|
+
// architecture, "The real-system adapters").
|
|
3
4
|
import { packOf, registerPack } from '@volter/world-core';
|
|
4
5
|
import { manifest } from "./manifest.js";
|
|
5
6
|
import { manifest as apiManifest } from "../api/src/manifest.js";
|
package/dist/src/manifest.js
CHANGED
|
@@ -18,12 +18,13 @@ export const manifest = {
|
|
|
18
18
|
bin: 'world-sentry',
|
|
19
19
|
resources: ['organization', 'project', 'project_key', 'event', 'issue', 'release', 'monitor', 'check_in'],
|
|
20
20
|
specSource: "Sentry's OpenAPI document (api/spec, sentry-api-schema 98ec014, patched with evidence) and its SDK developer documentation's envelope endpoint (ingest/spec)",
|
|
21
|
-
description: 'Sentry twin — the envelope endpoint every SDK sends to (events grouped into issues, transactions, sessions, replays, profiles, check-ins, feedback, logs, metrics), and release and source-map APIs used by Cal.com
|
|
21
|
+
description: 'Sentry twin — the envelope endpoint every SDK sends to (events grouped into issues, transactions, sessions, replays, profiles, check-ins, feedback, logs, metrics), and release and source-map APIs used by Cal.com and Rallly, account project/key setup, and core vendor-backed reads.',
|
|
22
22
|
adoption: { sdks: ['@sentry/astro', '@sentry/browser', '@sentry/electron', '@sentry/nextjs', '@sentry/node', '@sentry/nestjs', '@sentry/react', '@sentry/react-native', '@sentry/remix', '@sentry/sveltekit', '@sentry/vue', '@sentry/cli'], pypi: ['sentry-sdk'], envStems: ['APPSMITHSENTRY', 'SENTRY'] },
|
|
23
23
|
hosts: [{ host: 'sentry.io' }, { host: 'us.sentry.io' }, { host: 'de.sentry.io' }, { suffix: '.ingest.sentry.io' }, { suffix: '.ingest.us.sentry.io' }, { suffix: '.ingest.de.sentry.io' }],
|
|
24
24
|
endpointEnvNone: "an SDK sends to its DSN's host and sentry-cli to SENTRY_URL (sentry.io by default); the DSN the World issues names the World's ingest host, claimed by the host rules",
|
|
25
25
|
// Bootstrap: account sign-up, first credential and the project/key named by this env, stable at every boot.
|
|
26
|
-
credentialDoor: {
|
|
26
|
+
credentialDoor: { account: { field: 'account', tenancy: 'organization', source: 'https://docs.sentry.io/api/permissions/' },
|
|
27
|
+
deployment: { field: 'deployment', object: 'project and client key', source: 'https://docs.sentry.io/product/sentry-basics/environments/', organizationOutputs: ['SENTRY_ORG', 'SENTRY_ORGANIZATION'] },
|
|
27
28
|
path: '/_twin/app-credentials', body: {},
|
|
28
29
|
fill: {
|
|
29
30
|
SENTRY_DSN: 'dsn', NEXT_PUBLIC_SENTRY_DSN: 'dsn', NEXT_PUBLIC_SENTRY_DSN_CLIENT: 'dsn', SENTRY_FRONT_DSN: 'dsn', SENTRY_DSN_API: 'dsn', VITE_SENTRY_DSN: 'dsn', EXPO_PUBLIC_SENTRY_DSN: 'dsn',
|
|
@@ -2,14 +2,15 @@ import type { HandlerContext } from '@volter/world-core';
|
|
|
2
2
|
type Reader = Pick<HandlerContext, 'row' | 'rowsRaw' | 'own'>;
|
|
3
3
|
export type Row = Record<string, unknown>;
|
|
4
4
|
/** An organization, made the first time (sentry.io's sign-up names it). */
|
|
5
|
-
export declare function makeOrg(ctx: HandlerContext, slug: string, name: string): Promise<Row>;
|
|
5
|
+
export declare function makeOrg(ctx: HandlerContext, slug: string, name: string, owner: string): Promise<Row | Response>;
|
|
6
6
|
/** A project of an organization with its default client key (the project's creation makes one). */
|
|
7
|
-
export declare function makeProject(ctx: HandlerContext, org: Row, slug: string, platform: string | null): Promise<Row>;
|
|
7
|
+
export declare function makeProject(ctx: HandlerContext, org: Row, slug: string, platform: string | null): Promise<Row | Response>;
|
|
8
|
+
export declare function makeClientKey(ctx: HandlerContext, org: Row, project: Row): Promise<Row>;
|
|
8
9
|
/** An organization auth token, made on Settings > Auth Tokens: its value shown once. Its scopes are the organization
|
|
9
10
|
* token's fixed set ("org:ci": releases and source maps, and reading the organization). */
|
|
10
11
|
export declare function makeOrgToken(ctx: HandlerContext, org: Row, name: string, scopes?: string[]): Promise<string>;
|
|
11
|
-
/** A
|
|
12
|
-
export declare function
|
|
12
|
+
/** A live native auth token, reconstructed only while its stored digest still matches. */
|
|
13
|
+
export declare function readToken(ctx: HandlerContext, org: Row, row: Row): Promise<string | undefined>;
|
|
13
14
|
/** A personal token (User Settings > Personal Tokens): `sntryu_` and 64 hexadecimal digits, with the scopes chosen when it
|
|
14
15
|
* is made, acting on the organizations its user belongs to (here, the one named). */
|
|
15
16
|
export declare function makePersonalToken(ctx: HandlerContext, org: Row, name: string, scopes: string[]): Promise<string>;
|
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
/** An organization, made the first time (sentry.io's sign-up names it). */
|
|
2
|
-
export async function makeOrg(ctx, slug, name) {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
return held;
|
|
6
|
-
const id = ctx.mint(ORG);
|
|
7
|
-
await ctx.write(ORG, id, { id, slug, name, dateCreated: ctx.occurredAt, status: { id: 'active', name: 'active' }, avatar: { avatarType: 'letter_avatar', avatarUuid: null }, isEarlyAdopter: false, require2FA: false, hasAuthProvider: false, features: [...DEFAULT_FEATURES], links: { organizationUrl: `https://${slug}.sentry.io`, regionUrl: 'https://us.sentry.io' }, allowMemberInvite: true, allowMemberProjectCreation: true, allowSuperuserAccess: false }, 'organization.create');
|
|
8
|
-
return ctx.row(ORG, id);
|
|
2
|
+
export async function makeOrg(ctx, slug, name, owner) {
|
|
3
|
+
const made = await ctx.createUnique(ORG, { slug, name, _owner: owner, dateCreated: ctx.occurredAt, status: { id: 'active', name: 'active' }, avatar: { avatarType: 'letter_avatar', avatarUuid: null }, isEarlyAdopter: false, require2FA: false, hasAuthProvider: false, features: [...DEFAULT_FEATURES], links: { organizationUrl: `https://${slug}.sentry.io`, regionUrl: 'https://us.sentry.io' }, allowMemberInvite: true, allowMemberProjectCreation: true, allowSuperuserAccess: false }, 'organization.create');
|
|
4
|
+
return made.created ? ctx.row(ORG, String(made.row.id)) : Response.json({ slug: ['An organization with this slug already exists.'] }, { status: 409 });
|
|
9
5
|
}
|
|
10
6
|
/** A project of an organization with its default client key (the project's creation makes one). */
|
|
11
7
|
export async function makeProject(ctx, org, slug, platform) {
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
return
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
const made = await ctx.createUnique(PROJECT, { slug, name: slug, platform, _organization: org.id, dateCreated: ctx.occurredAt, _issueCounter: 0, isBookmarked: false, isMember: true, features: ['releases'], firstEvent: null, firstTransactionEvent: false, access: [], hasAccess: true, team: null, teams: [], environments: [], latestRelease: null, platforms: platform ? [platform] : [], hasSessions: false, hasProfiles: false, hasReplays: false, hasFlags: false, hasMinifiedStackTrace: false, hasMonitors: false, hasFeedbacks: false, hasNewFeedbacks: false, hasUserReports: false, hasInsightsHttp: false, hasInsightsDb: false, hasInsightsAssets: false, hasInsightsAppStart: false, hasInsightsScreenLoad: false, hasInsightsVitals: false, hasInsightsCaches: false, hasInsightsQueues: false, hasInsightsAgentMonitoring: false, hasInsightsMCP: false, hasLogs: false, hasTraceMetrics: false }, 'project.create');
|
|
9
|
+
if (!made.created)
|
|
10
|
+
return Response.json({ slug: ['A project with this slug already exists.'] }, { status: 409 });
|
|
11
|
+
const project = ctx.row(PROJECT, String(made.row.id));
|
|
12
|
+
await makeClientKey(ctx, org, project);
|
|
13
|
+
return project;
|
|
14
|
+
}
|
|
15
|
+
export async function makeClientKey(ctx, org, project) {
|
|
16
|
+
const id = String(project.id);
|
|
19
17
|
// source: https://raw.githubusercontent.com/getsentry/sentry/2efdd4760a75a3d395e3534b7f66a7fbb0c3f22e/src/sentry/models/projectkey.py "secret_key = ProjectKey.generate_api_key()"
|
|
20
|
-
const secret = ctx.crypto.digest('sha256', await ctx.secret(`sentry-project-key:${
|
|
18
|
+
const secret = ctx.crypto.digest('sha256', await ctx.secret(`sentry-project-key:${await next(ctx, 'project_key')}`), 'hex').slice(0, 32);
|
|
21
19
|
const endpoint = `https://o${String(org.id)}.ingest.us.sentry.io`;
|
|
22
20
|
const api = `${endpoint}/api/${id}`;
|
|
23
21
|
// source: https://raw.githubusercontent.com/getsentry/sentry/2efdd4760a75a3d395e3534b7f66a7fbb0c3f22e/src/sentry/api/serializers/models/project_key.py "ProjectKeySerializerResponse"
|
|
24
22
|
// source: https://raw.githubusercontent.com/getsentry/sentry/2efdd4760a75a3d395e3534b7f66a7fbb0c3f22e/src/sentry/projectoptions/defaults.py "sentry:default_loader_version"
|
|
25
23
|
// New projects take the current option epoch's loader defaults; the installed application SDK is independent.
|
|
26
|
-
await ctx.
|
|
24
|
+
const made = await ctx.create(CLIENT_KEY, (pub) => ({ id: pub, public: pub, secret, _project: id, name: 'Default', label: 'Default', projectId: Number(id), isActive: true, dateCreated: ctx.occurredAt, rateLimit: null,
|
|
27
25
|
dsn: { public: `https://${pub}@${endpoint.slice(8)}/${id}`, secret: `https://${pub}:${secret}@${endpoint.slice(8)}/${id}`,
|
|
28
26
|
csp: `${api}/csp-report/?sentry_key=${pub}`, security: `${api}/security/?sentry_key=${pub}`, minidump: `${api}/minidump/?sentry_key=${pub}`, nel: `${api}/nel/?sentry_key=${pub}`,
|
|
29
27
|
playstation: `${api}/playstation/?sentry_key=${pub}`, integration: `${api}/integration/`, otlp_traces: `${api}/integration/otlp/v1/traces`, otlp_logs: `${api}/integration/otlp/v1/logs`, unreal: `${api}/unreal/${pub}/`, crons: `${api}/cron/___MONITOR_SLUG___/${pub}/`, cdn: `https://js.sentry-cdn.com/${pub}.min.js` },
|
|
30
28
|
browserSdkVersion: '11.x', browserSdk: { choices: ['11.x', '10.x', '9.x', '8.x', '7.x'].map((v) => [v, v]) }, dynamicSdkLoaderOptions: { hasReplay: true, hasPerformance: true, hasDebug: false, hasFeedback: false, hasLogsAndMetrics: false },
|
|
31
|
-
}, 'project_key.create');
|
|
32
|
-
return ctx.row(
|
|
29
|
+
}), 'project_key.create');
|
|
30
|
+
return ctx.row(CLIENT_KEY, String(made.id));
|
|
33
31
|
}
|
|
34
32
|
/** An organization auth token's value: `sntrys_`, the base64 of what it is for (when it was made, the API's and the
|
|
35
33
|
* region's URLs, the organization) and `_` and its secret, drawn from the World's secret under its label. */
|
|
@@ -47,12 +45,10 @@ export async function makeOrgToken(ctx, org, name, scopes = ['org:ci']) {
|
|
|
47
45
|
await ctx.record(TOKEN, { organization: org.id, name, scopes, kind: 'org', dateCreated: ctx.occurredAt, iat, label, lastChars: token.slice(-4) }, sha256(ctx, token));
|
|
48
46
|
return token;
|
|
49
47
|
}
|
|
50
|
-
/** A
|
|
51
|
-
export async function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
async function reissueHeldToken(ctx, org, row) {
|
|
48
|
+
/** A live native auth token, reconstructed only while its stored digest still matches. */
|
|
49
|
+
export async function readToken(ctx, org, row) {
|
|
50
|
+
if (row.deleted === true || row.organization !== org.id)
|
|
51
|
+
return undefined;
|
|
56
52
|
const token = row.kind === 'personal' ? `sntryu_${ctx.crypto.digest('sha256', await ctx.secret(String(row.label)), 'hex')}` : await tokenValue(ctx, org, String(row.label), Number(row.iat));
|
|
57
53
|
return sha256(ctx, token) === row.id ? token : undefined;
|
|
58
54
|
}
|
|
@@ -62,7 +58,7 @@ export async function makePersonalToken(ctx, org, name, scopes) {
|
|
|
62
58
|
const label = `sentry-personal-token:${await next(ctx, 'token')}`;
|
|
63
59
|
const hex = ctx.crypto.digest('sha256', await ctx.secret(label), 'hex');
|
|
64
60
|
const token = `sntryu_${hex}`;
|
|
65
|
-
await ctx.record(TOKEN, { organization: org.id, name, scopes, kind: 'personal', dateCreated: ctx.occurredAt, label, lastChars: token.slice(-4) }, sha256(ctx, token));
|
|
61
|
+
await ctx.record(TOKEN, { organization: org.id, user: org._owner, name, scopes, kind: 'personal', dateCreated: ctx.occurredAt, label, lastChars: token.slice(-4) }, sha256(ctx, token));
|
|
66
62
|
return token;
|
|
67
63
|
}
|
|
68
64
|
/** The token a request's `Authorization: Bearer` names, if the World issued it and it is not revoked. */
|
|
@@ -288,7 +284,7 @@ export function issueView(ctx, raw) {
|
|
|
288
284
|
}
|
|
289
285
|
/** Stored vendor fields, including the API's own _meta; private association/payload fields stay in the tree. */
|
|
290
286
|
export function wire(ctx, row) {
|
|
291
|
-
return Object.fromEntries(Object.entries(ctx.own(row)).filter(([key]) => !key.startsWith('_') || key === '_meta'));
|
|
287
|
+
return Object.fromEntries(Object.entries({ id: row.id, ...ctx.own(row) }).filter(([key]) => !key.startsWith('_') || key === '_meta'));
|
|
292
288
|
}
|
|
293
289
|
export const ORG = 'organization';
|
|
294
290
|
export const PROJECT = 'project';
|
|
@@ -64,6 +64,20 @@
|
|
|
64
64
|
],
|
|
65
65
|
"endpointEnvNone": "an SDK sends to its DSN's host and sentry-cli to SENTRY_URL (sentry.io by default); the DSN the World issues names the World's ingest host, claimed by the host rules",
|
|
66
66
|
"credentialDoor": {
|
|
67
|
+
"account": {
|
|
68
|
+
"field": "account",
|
|
69
|
+
"tenancy": "organization",
|
|
70
|
+
"source": "https://docs.sentry.io/api/permissions/"
|
|
71
|
+
},
|
|
72
|
+
"deployment": {
|
|
73
|
+
"field": "deployment",
|
|
74
|
+
"object": "project and client key",
|
|
75
|
+
"source": "https://docs.sentry.io/product/sentry-basics/environments/",
|
|
76
|
+
"organizationOutputs": [
|
|
77
|
+
"SENTRY_ORG",
|
|
78
|
+
"SENTRY_ORGANIZATION"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
67
81
|
"path": "/_twin/app-credentials",
|
|
68
82
|
"body": {},
|
|
69
83
|
"fill": {
|
package/ingest/src/manifest.ts
CHANGED
|
@@ -7,6 +7,9 @@ export const manifest: DerivedManifest = {
|
|
|
7
7
|
vendor: 'sentry',
|
|
8
8
|
// one service with the API lane (../../api/src/manifest.ts), so one store
|
|
9
9
|
service: 'sentry',
|
|
10
|
+
hosts: [{ suffix: '.ingest.sentry.io' }, { suffix: '.ingest.us.sentry.io' }, { suffix: '.ingest.de.sentry.io' }],
|
|
11
|
+
// source: spec:/servers/0/url "https://o0.ingest.us.sentry.io"
|
|
12
|
+
remoteHost: { template: 'o0.ingest.us.sentry.io' },
|
|
10
13
|
// an envelope is not JSON: its bytes are kept (./semantics/around.ts) and read by the handler
|
|
11
14
|
body: {},
|
|
12
15
|
// source: https://raw.githubusercontent.com/getsentry/sentry-docs/3ec4e139711af82b38a3c2c17261f5f5cdb66dfc/develop-docs/sdk/foundations/transport/compression.mdx "The following content encodings are accepted by Relay and Sentry:"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"vendor": "sentry",
|
|
3
3
|
"author": "Contributor 93",
|
|
4
|
-
"about": "Harbourline runs Rallly and Twenty, uses
|
|
4
|
+
"about": "Harbourline runs Rallly and Twenty, uses Scheduler for social channels and Cal.com for booking. Account setup uses the API after bootstrap; releases, source maps and ingestion follow the pinned application clients.",
|
|
5
5
|
"steps": [
|
|
6
6
|
{
|
|
7
7
|
"name": "Monday 5 January, 09:00: Harbourline puts Sentry on Rallly and Twenty",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"evidence": "spec:listProjectKeys"
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
|
-
"name": "Priya creates the
|
|
108
|
+
"name": "Priya creates the scheduler project through Sentry API",
|
|
109
109
|
"method": "POST",
|
|
110
110
|
"path": "https://sentry.io/api/0/organizations/harbourline/projects/",
|
|
111
111
|
"status": 201,
|
|
@@ -113,8 +113,8 @@
|
|
|
113
113
|
"authorization": "Bearer {ci}"
|
|
114
114
|
},
|
|
115
115
|
"body": {
|
|
116
|
-
"name": "
|
|
117
|
-
"slug": "
|
|
116
|
+
"name": "scheduler",
|
|
117
|
+
"slug": "scheduler",
|
|
118
118
|
"platform": "javascript-nextjs"
|
|
119
119
|
},
|
|
120
120
|
"capture": {
|
|
@@ -123,11 +123,11 @@
|
|
|
123
123
|
"expect": [
|
|
124
124
|
{
|
|
125
125
|
"path": "slug",
|
|
126
|
-
"equals": "
|
|
126
|
+
"equals": "scheduler"
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
129
|
"path": "name",
|
|
130
|
-
"equals": "
|
|
130
|
+
"equals": "scheduler"
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
"path": "platform",
|
|
@@ -137,9 +137,9 @@
|
|
|
137
137
|
"evidence": "spec:createOrganizationProject"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
|
-
"name": "She reads the
|
|
140
|
+
"name": "She reads the scheduler DSN from its client key",
|
|
141
141
|
"method": "GET",
|
|
142
|
-
"path": "https://sentry.io/api/0/projects/harbourline/
|
|
142
|
+
"path": "https://sentry.io/api/0/projects/harbourline/scheduler/keys/",
|
|
143
143
|
"status": 200,
|
|
144
144
|
"headers": {
|
|
145
145
|
"authorization": "Bearer {ci}"
|
|
@@ -485,7 +485,7 @@
|
|
|
485
485
|
"client": "@sentry-internal/replay@10.38.0:build/npm/esm/index.js:8039"
|
|
486
486
|
},
|
|
487
487
|
{
|
|
488
|
-
"name": "A
|
|
488
|
+
"name": "A Scheduler user sends feedback from its configured widget",
|
|
489
489
|
"method": "POST",
|
|
490
490
|
"path": "https://{r_host}/api/{p_proj}/envelope/",
|
|
491
491
|
"status": 200,
|
|
@@ -493,18 +493,17 @@
|
|
|
493
493
|
"x-sentry-auth": "Sentry sentry_version=7, sentry_client=sentry.javascript.nextjs/10.56.0, sentry_key={p_key}",
|
|
494
494
|
"content-type": "application/x-sentry-envelope"
|
|
495
495
|
},
|
|
496
|
-
"body": "{\"event_id\":\"fb0000000000400080000000000000a1\",\"sent_at\":\"2026-01-06T14:08:00.000Z\",\"sdk\":{\"name\":\"sentry.javascript.nextjs\",\"version\":\"10.56.0\"}}\n{\"type\":\"feedback\"}\n{\"event_id\":\"fb0000000000400080000000000000a1\",\"timestamp\":\"2026-01-06T14:08:00.000Z\",\"level\":\"info\",\"platform\":\"javascript\",\"contexts\":{\"feedback\":{\"message\":\"The scheduling button is hard to find.\",\"contact_email\":\"u_1182@harbourline.test\",\"name\":\"Dana Ito\",\"url\":\"https://
|
|
496
|
+
"body": "{\"event_id\":\"fb0000000000400080000000000000a1\",\"sent_at\":\"2026-01-06T14:08:00.000Z\",\"sdk\":{\"name\":\"sentry.javascript.nextjs\",\"version\":\"10.56.0\"}}\n{\"type\":\"feedback\"}\n{\"event_id\":\"fb0000000000400080000000000000a1\",\"timestamp\":\"2026-01-06T14:08:00.000Z\",\"level\":\"info\",\"platform\":\"javascript\",\"contexts\":{\"feedback\":{\"message\":\"The scheduling button is hard to find.\",\"contact_email\":\"u_1182@harbourline.test\",\"name\":\"Dana Ito\",\"url\":\"https://scheduler.harbourline.test/launches\"}},\"user\":{\"id\":\"u_1182\",\"email\":\"u_1182@harbourline.test\",\"username\":\"Dana Ito\"},\"sdk\":{\"name\":\"sentry.javascript.nextjs\",\"version\":\"10.56.0\"},\"type\":\"feedback\"}\n",
|
|
497
497
|
"expect": [
|
|
498
498
|
{
|
|
499
499
|
"path": "id",
|
|
500
500
|
"equals": "fb0000000000400080000000000000a1"
|
|
501
501
|
}
|
|
502
502
|
],
|
|
503
|
-
"evidence": "gitroomhq/postiz-app@e0a08a7d9592:apps/frontend/src/components/new-layout/sentry.feedback.component.tsx:24",
|
|
504
503
|
"client": "@sentry/core@10.56.0:build/esm/feedback.js:6"
|
|
505
504
|
},
|
|
506
505
|
{
|
|
507
|
-
"name": "Harbourline schedules a
|
|
506
|
+
"name": "Harbourline schedules a Scheduler post and the service counts post_created",
|
|
508
507
|
"method": "POST",
|
|
509
508
|
"path": "https://{r_host}/api/{p_proj}/envelope/",
|
|
510
509
|
"status": 200,
|
|
@@ -513,7 +512,6 @@
|
|
|
513
512
|
"content-type": "application/x-sentry-envelope"
|
|
514
513
|
},
|
|
515
514
|
"body": "{\"sent_at\":\"2026-01-06T14:30:00.000Z\",\"sdk\":{\"name\":\"sentry.javascript.nestjs\",\"version\":\"10.56.0\"}}\n{\"type\":\"trace_metric\",\"item_count\":1,\"content_type\":\"application/vnd.sentry.items.trace-metric+json\"}\n{\"items\":[{\"timestamp\":1767709800,\"trace_id\":\"abcdef0123456789abcdef0123456789\",\"name\":\"post_created\",\"type\":\"counter\",\"unit\":\"none\",\"value\":1,\"attributes\":{\"sentry.environment\":{\"value\":\"production\",\"type\":\"string\"},\"service\":{\"value\":\"backend\",\"type\":\"string\"}}}],\"version\":2}\n",
|
|
516
|
-
"evidence": "gitroomhq/postiz-app@e0a08a7d9592:libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts:994",
|
|
517
515
|
"client": "@sentry/core@10.56.0:build/esm/metrics/envelope.js:12"
|
|
518
516
|
},
|
|
519
517
|
{
|
|
@@ -5,7 +5,7 @@ Author: Contributor 93
|
|
|
5
5
|
## The customer
|
|
6
6
|
|
|
7
7
|
**Harbourline** runs Rallly (lukevella/rallly at `3e63239dbdec`) and Twenty (twentyhq/twenty at `1d561b8c73c9`) for its team,
|
|
8
|
-
and
|
|
8
|
+
and Scheduler for its social channels, each reporting to Sentry (../journeys/demand.json):
|
|
9
9
|
|
|
10
10
|
- **Rallly**: `@sentry/nextjs` 10.38 on the server, edge and browser (errors, transactions, logs, Session Replay; the
|
|
11
11
|
browser envelopes directly to its DSN host (tunnelRoute is commented out)), and at build `withSentryConfig`, whose sentry-cli makes the release,
|
|
@@ -13,7 +13,7 @@ and Postiz (gitroomhq/postiz-app at `e0a08a7d9592`) for its social channels, eac
|
|
|
13
13
|
- **Twenty**: `@sentry/nestjs` 10.73 on the server (errors, transactions, profiles), its cron jobs checked in as
|
|
14
14
|
monitors (`captureCheckIn` with the schedule).
|
|
15
15
|
|
|
16
|
-
Harbourline also runs Cal.com (calcom/cal.diy at `54343aa685ae`) for bookings. Its release script reads SENTRY_AUTH_TOKEN, makes its git HEAD release, associates local commits, injects source-map debug ids, uploads the bundle and finalizes.
|
|
16
|
+
Harbourline also runs Cal.com (calcom/cal.diy at `54343aa685ae`) for bookings. Its release script reads SENTRY_AUTH_TOKEN, makes its git HEAD release, associates local commits, injects source-map debug ids, uploads the bundle and finalizes. Scheduler pins Sentry 10.56.0.
|
|
17
17
|
|
|
18
18
|
The World clock starts 2026-01-01.
|
|
19
19
|
|
|
@@ -23,12 +23,12 @@ The World clock starts 2026-01-01.
|
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| Priya Raman, Harbourline's engineer | the harbourline organization | bootstrap sign-up; additional projects and keys through the API; token issuance door |
|
|
25
25
|
| Rallly and Cal builds | the bootstrap token and Cal CI token | sentry-cli 2.58.4 |
|
|
26
|
-
| Rallly, Twenty and
|
|
26
|
+
| Rallly, Twenty and Scheduler in production | their projects' DSNs | the SDKs' envelopes |
|
|
27
27
|
|
|
28
28
|
## The arc
|
|
29
29
|
|
|
30
30
|
### 1. Setting up (Mon 5 Jan)
|
|
31
|
-
The bootstrap makes the organization, first auth token and rallly project/DSN. Priya issues Cal's CI auth token for SENTRY_AUTH_TOKEN. With her bootstrap token she creates twenty and
|
|
31
|
+
The bootstrap makes the organization, first auth token and rallly project/DSN. Priya issues Cal's CI auth token for SENTRY_AUTH_TOKEN. With her bootstrap token she creates twenty and scheduler through createOrganizationProject, and reads their DSNs through listProjectKeys. She creates cal for the booking app.
|
|
32
32
|
|
|
33
33
|
### 2. Rallly's release (Tue 6 Jan, 10:00)
|
|
34
34
|
sentry-cli makes `rallly@4.2.0` (a retry answers 208), reads the chunk
|
|
@@ -38,7 +38,7 @@ finalizes the release.
|
|
|
38
38
|
### 3. Production (Tue 6 Jan, 14:30)
|
|
39
39
|
A browser session; an unhandled rejection with its stack trace, and the same error for another visitor in a gzipped
|
|
40
40
|
envelope (one issue, two users); a transaction; a replay of the visitor who hit
|
|
41
|
-
the error; a
|
|
41
|
+
the error; a Scheduler user sends feedback from its configured widget; Scheduler's post_created metric; the server's logs and minute of HTTP request-session aggregates; a client report for sampled-out transactions.
|
|
42
42
|
|
|
43
43
|
### 4. Twenty's cron (Wed 7 Jan, 03:00)
|
|
44
44
|
The cleanup job checks in in progress with its schedule (the monitor is made), then ok using the same check-in id and no explicit duration (checkin_margin 1, max_runtime 5); a worker
|