@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.
Files changed (44) hide show
  1. package/README.md +3 -2
  2. package/api/spec/SOURCE.md +19 -2
  3. package/api/spec/openapi.json.gz +0 -0
  4. package/api/src/generated/ui.gen.json +1 -1
  5. package/api/src/manifest.ts +4 -3
  6. package/api/src/semantics/api.ts +3 -2
  7. package/api/src/semantics/doors.ts +45 -19
  8. package/api/src/semantics/shared.ts +1 -5
  9. package/dist/api/spec/SOURCE.md +19 -2
  10. package/dist/api/spec/openapi.json.gz +0 -0
  11. package/dist/api/src/generated/ui.gen.json +1 -1
  12. package/dist/api/src/manifest.js +4 -3
  13. package/dist/api/src/manifest.ts +4 -3
  14. package/dist/api/src/semantics/api.js +4 -2
  15. package/dist/api/src/semantics/api.ts +3 -2
  16. package/dist/api/src/semantics/doors.d.ts +1 -4
  17. package/dist/api/src/semantics/doors.js +51 -19
  18. package/dist/api/src/semantics/doors.ts +45 -19
  19. package/dist/api/src/semantics/shared.d.ts +0 -1
  20. package/dist/api/src/semantics/shared.js +1 -2
  21. package/dist/api/src/semantics/shared.ts +1 -5
  22. package/dist/ingest/src/manifest.js +3 -0
  23. package/dist/ingest/src/manifest.ts +3 -0
  24. package/dist/journeys/customer-life.json +11 -13
  25. package/dist/journeys/customer-life.outline.md +5 -5
  26. package/dist/journeys/demand.json +1 -45
  27. package/dist/journeys/first-use.json +22 -0
  28. package/dist/src/cli.js +1 -1
  29. package/dist/src/index.js +2 -1
  30. package/dist/src/manifest.js +3 -2
  31. package/dist/src/semantics/shared.d.ts +5 -4
  32. package/dist/src/semantics/shared.js +22 -26
  33. package/generated/pack-facts.json +14 -0
  34. package/ingest/src/manifest.ts +3 -0
  35. package/journeys/customer-life.json +11 -13
  36. package/journeys/customer-life.outline.md +5 -5
  37. package/journeys/demand.json +1 -45
  38. package/journeys/first-use.json +22 -0
  39. package/package.json +10 -9
  40. package/src/cli.ts +1 -1
  41. package/src/index.ts +2 -1
  42. package/src/manifest.ts +3 -2
  43. package/src/semantics/shared.ts +22 -26
  44. package/dist/generated/pack-facts.json +0 -87
@@ -1,4 +1,4 @@
1
- import { bearerToken, grants, orgBySlug, projectOf, wire } from "../../../src/semantics/shared.js";
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, wire } from '../../../src/semantics/shared.ts';
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 Postiz 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.",
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 postiz project through Sentry API",
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": "postiz",
117
- "slug": "postiz",
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": "postiz"
126
+ "equals": "scheduler"
127
127
  },
128
128
  {
129
129
  "path": "name",
130
- "equals": "postiz"
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 postiz DSN from its client key",
140
+ "name": "She reads the scheduler DSN from its client key",
141
141
  "method": "GET",
142
- "path": "https://sentry.io/api/0/projects/harbourline/postiz/keys/",
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 Postiz user sends feedback from its configured widget",
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://postiz.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",
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 Postiz post and the service counts post_created",
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 Postiz (gitroomhq/postiz-app at `e0a08a7d9592`) for its social channels, each reporting to Sentry (../journeys/demand.json):
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. Postiz pins Sentry 10.56.0.
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 Postiz in production | their projects' DSNs | the SDKs' envelopes |
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 postiz through createOrganizationProject, and reads their DSNs through listProjectKeys. She creates cal for the booking app.
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 Postiz user sends feedback from its configured widget; Postiz's post_created metric; the server's logs and minute of HTTP request-session aggregates; a client report for sampled-out transactions.
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 World's managed Postgres, which the runtime binds a pack declaring managedDatabase to
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 (architecture, "The real-system adapters").
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";
@@ -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, Postiz and Rallly, account project/key setup, and core vendor-backed reads.',
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 token the organization holds by its name, answered again only while its derived value still matches its hash. */
12
- export declare function heldOrgToken(ctx: HandlerContext, org: Row, name: string): Promise<string | undefined>;
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 held = orgBySlug(ctx, slug);
4
- if (held)
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 held = projectOf(ctx, org, slug);
13
- if (held)
14
- return held;
15
- const id = ctx.mint(PROJECT);
16
- const project = await ctx.write(PROJECT, id, { id, 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');
17
- // a client key's public part: 32 hexadecimal characters, drawn from the World's secret for the project
18
- const pub = ctx.mint(CLIENT_KEY);
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:${pub}`), 'hex').slice(0, 32);
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.write(CLIENT_KEY, pub, { id: pub, public: pub, secret, _project: id, name: 'Default', label: 'Default', projectId: Number(id), isActive: true, dateCreated: ctx.occurredAt, rateLimit: null,
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(PROJECT, id);
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 token the organization holds by its name, answered again only while its derived value still matches its hash. */
51
- export async function heldOrgToken(ctx, org, name) {
52
- const row = ctx.rowsRaw(TOKEN).find((t) => t.deleted !== true && t.organization === org.id && t.name === name);
53
- return row ? reissueHeldToken(ctx, org, row) : undefined;
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": {
@@ -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 Postiz 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.",
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 postiz project through Sentry API",
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": "postiz",
117
- "slug": "postiz",
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": "postiz"
126
+ "equals": "scheduler"
127
127
  },
128
128
  {
129
129
  "path": "name",
130
- "equals": "postiz"
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 postiz DSN from its client key",
140
+ "name": "She reads the scheduler DSN from its client key",
141
141
  "method": "GET",
142
- "path": "https://sentry.io/api/0/projects/harbourline/postiz/keys/",
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 Postiz user sends feedback from its configured widget",
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://postiz.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",
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 Postiz post and the service counts post_created",
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 Postiz (gitroomhq/postiz-app at `e0a08a7d9592`) for its social channels, each reporting to Sentry (../journeys/demand.json):
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. Postiz pins Sentry 10.56.0.
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 Postiz in production | their projects' DSNs | the SDKs' envelopes |
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 postiz through createOrganizationProject, and reads their DSNs through listProjectKeys. She creates cal for the booking app.
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 Postiz user sends feedback from its configured widget; Postiz's post_created metric; the server's logs and minute of HTTP request-session aggregates; a client report for sampled-out transactions.
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