@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,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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-sentry",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Sentry Protocol 3 twin: demanded SDK envelopes, release/source-map APIs, account project and client-key setup, and derived vendor-backed reads.",
5
5
  "keywords": [
6
6
  "twin",
@@ -26,24 +26,25 @@
26
26
  ],
27
27
  "repository": {
28
28
  "type": "git",
29
- "url": "git+https://github.com/volter-ai/twin-packs-p3.git",
29
+ "url": "git+https://github.com/volter-ai/twin-packs-open.git",
30
30
  "directory": "sentry"
31
31
  },
32
- "homepage": "https://github.com/volter-ai/twin-packs-p3/tree/main/sentry#readme",
32
+ "homepage": "https://github.com/volter-ai/twin-packs-open/tree/main/sentry#readme",
33
33
  "type": "module",
34
34
  "exports": {
35
- ".": {
36
- "types": "./dist/src/index.d.ts",
37
- "default": "./dist/src/index.js"
38
- }
35
+ ".": "./dist/src/index.js"
39
36
  },
40
37
  "bin": {
41
- "world-sentry": "dist/src/cli.js"
38
+ "world-sentry": "./dist/src/cli.js"
42
39
  },
43
40
  "peerDependencies": {
44
- "@volter/world-core": "^3.0.38"
41
+ "@volter/world-core": ">=3.0.163",
42
+ "@volter/world-runtime": ">=3.0.184"
45
43
  },
46
44
  "engines": {
47
45
  "node": ">=22.3"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/volter-ai/twin-packs-open/issues"
48
49
  }
49
50
  }
package/src/cli.ts CHANGED
@@ -8,7 +8,7 @@ const [cmd, ...rest] = process.argv.slice(2);
8
8
  const port = Number(optionValue(rest, '--port', '0')) || undefined;
9
9
  const root = optionValue(rest, '--root') || undefined;
10
10
  const readOnly = hasFlag(rest, '--read-only');
11
- // the World's managed Postgres, which the runtime binds a pack declaring managedDatabase to
11
+ // the selected twin's private Postgres, which the runtime binds a pack declaring managedDatabase to
12
12
  const database = optionValue(rest, '--database') || undefined;
13
13
  // the World's scenario file, for a model vendor's turns
14
14
  const scenarioPath = optionValue(rest, '--scenario') || undefined;
package/src/index.ts 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, type DerivedSurface } from '@volter/world-core';
4
5
  import { manifest } from './manifest.ts';
5
6
  import { manifest as apiManifest } from '../api/src/manifest.ts';
package/src/manifest.ts CHANGED
@@ -24,12 +24,13 @@ export const manifest: VendorManifest = {
24
24
  bin: 'world-sentry',
25
25
  resources: ['organization', 'project', 'project_key', 'event', 'issue', 'release', 'monitor', 'check_in'],
26
26
  specSource: "Sentry's OpenAPI document (api/spec, sentry-api-schema 98ec014, patched with evidence) and its SDK developer documentation's envelope endpoint (ingest/spec)",
27
- 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.',
27
+ 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.',
28
28
  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'] },
29
29
  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' }],
30
30
  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",
31
31
  // Bootstrap: account sign-up, first credential and the project/key named by this env, stable at every boot.
32
- credentialDoor: {
32
+ credentialDoor: { account: { field: 'account', tenancy: 'organization', source: 'https://docs.sentry.io/api/permissions/' },
33
+ deployment: { field: 'deployment', object: 'project and client key', source: 'https://docs.sentry.io/product/sentry-basics/environments/', organizationOutputs: ['SENTRY_ORG', 'SENTRY_ORGANIZATION'] },
33
34
  path: '/_twin/app-credentials', body: {},
34
35
  fill: {
35
36
  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',
@@ -22,36 +22,36 @@ type Reader = Pick<HandlerContext, 'row' | 'rowsRaw' | 'own'>;
22
22
  export type Row = Record<string, unknown>;
23
23
 
24
24
  /** An organization, made the first time (sentry.io's sign-up names it). */
25
- export async function makeOrg(ctx: HandlerContext, slug: string, name: string): Promise<Row> {
26
- const held = orgBySlug(ctx, slug);
27
- if (held) return held;
28
- const id = ctx.mint(ORG);
29
- 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');
30
- return ctx.row(ORG, id)!;
25
+ export async function makeOrg(ctx: HandlerContext, slug: string, name: string, owner: string): Promise<Row | Response> {
26
+ 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');
27
+ return made.created ? ctx.row(ORG, String(made.row.id))! : Response.json({ slug: ['An organization with this slug already exists.'] }, { status: 409 });
31
28
  }
32
29
 
33
30
  /** A project of an organization with its default client key (the project's creation makes one). */
34
- export async function makeProject(ctx: HandlerContext, org: Row, slug: string, platform: string | null): Promise<Row> {
35
- const held = projectOf(ctx, org, slug);
36
- if (held) return held;
37
- const id = ctx.mint(PROJECT);
38
- 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');
39
- // a client key's public part: 32 hexadecimal characters, drawn from the World's secret for the project
40
- const pub = ctx.mint(CLIENT_KEY);
31
+ export async function makeProject(ctx: HandlerContext, org: Row, slug: string, platform: string | null): Promise<Row | Response> {
32
+ 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');
33
+ if (!made.created) return Response.json({ slug: ['A project with this slug already exists.'] }, { status: 409 });
34
+ const project = ctx.row(PROJECT, String(made.row.id))!;
35
+ await makeClientKey(ctx, org, project);
36
+ return project;
37
+ }
38
+
39
+ export async function makeClientKey(ctx: HandlerContext, org: Row, project: Row): Promise<Row> {
40
+ const id = String(project.id);
41
41
  // source: https://raw.githubusercontent.com/getsentry/sentry/2efdd4760a75a3d395e3534b7f66a7fbb0c3f22e/src/sentry/models/projectkey.py "secret_key = ProjectKey.generate_api_key()"
42
- const secret = ctx.crypto.digest('sha256', await ctx.secret(`sentry-project-key:${pub}`), 'hex').slice(0, 32);
42
+ const secret = ctx.crypto.digest('sha256', await ctx.secret(`sentry-project-key:${await next(ctx, 'project_key')}`), 'hex').slice(0, 32);
43
43
  const endpoint = `https://o${String(org.id)}.ingest.us.sentry.io`;
44
44
  const api = `${endpoint}/api/${id}`;
45
45
  // source: https://raw.githubusercontent.com/getsentry/sentry/2efdd4760a75a3d395e3534b7f66a7fbb0c3f22e/src/sentry/api/serializers/models/project_key.py "ProjectKeySerializerResponse"
46
46
  // source: https://raw.githubusercontent.com/getsentry/sentry/2efdd4760a75a3d395e3534b7f66a7fbb0c3f22e/src/sentry/projectoptions/defaults.py "sentry:default_loader_version"
47
47
  // New projects take the current option epoch's loader defaults; the installed application SDK is independent.
48
- 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,
48
+ 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,
49
49
  dsn: { public: `https://${pub}@${endpoint.slice(8)}/${id}`, secret: `https://${pub}:${secret}@${endpoint.slice(8)}/${id}`,
50
50
  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}`,
51
51
  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` },
52
52
  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 },
53
- }, 'project_key.create');
54
- return ctx.row(PROJECT, id)!;
53
+ }), 'project_key.create');
54
+ return ctx.row(CLIENT_KEY, String(made.id))!;
55
55
  }
56
56
 
57
57
  /** An organization auth token's value: `sntrys_`, the base64 of what it is for (when it was made, the API's and the
@@ -72,13 +72,9 @@ export async function makeOrgToken(ctx: HandlerContext, org: Row, name: string,
72
72
  return token;
73
73
  }
74
74
 
75
- /** A token the organization holds by its name, answered again only while its derived value still matches its hash. */
76
- export async function heldOrgToken(ctx: HandlerContext, org: Row, name: string): Promise<string | undefined> {
77
- const row = ctx.rowsRaw(TOKEN).find((t) => t.deleted !== true && t.organization === org.id && t.name === name);
78
- return row ? reissueHeldToken(ctx, org, row) : undefined;
79
- }
80
-
81
- async function reissueHeldToken(ctx: HandlerContext, org: Row, row: Row): Promise<string | undefined> {
75
+ /** A live native auth token, reconstructed only while its stored digest still matches. */
76
+ export async function readToken(ctx: HandlerContext, org: Row, row: Row): Promise<string | undefined> {
77
+ if (row.deleted === true || row.organization !== org.id) return undefined;
82
78
  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));
83
79
  return sha256(ctx, token) === row.id ? token : undefined;
84
80
  }
@@ -89,7 +85,7 @@ export async function makePersonalToken(ctx: HandlerContext, org: Row, name: str
89
85
  const label = `sentry-personal-token:${await next(ctx, 'token')}`;
90
86
  const hex = ctx.crypto.digest('sha256', await ctx.secret(label), 'hex');
91
87
  const token = `sntryu_${hex}`;
92
- await ctx.record(TOKEN, { organization: org.id, name, scopes, kind: 'personal', dateCreated: ctx.occurredAt, label, lastChars: token.slice(-4) }, sha256(ctx, token));
88
+ 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));
93
89
  return token;
94
90
  }
95
91
 
@@ -302,7 +298,7 @@ export function issueView(ctx: Reader, raw: Row): Row {
302
298
 
303
299
  /** Stored vendor fields, including the API's own _meta; private association/payload fields stay in the tree. */
304
300
  export function wire(ctx: Reader, row: Row): Row {
305
- return Object.fromEntries(Object.entries(ctx.own(row)).filter(([key]) => !key.startsWith('_') || key === '_meta'));
301
+ return Object.fromEntries(Object.entries({ id: row.id, ...ctx.own(row) }).filter(([key]) => !key.startsWith('_') || key === '_meta'));
306
302
  }
307
303
 
308
304
  export const ORG = 'organization';
@@ -1,87 +0,0 @@
1
- {
2
- "//": "GENERATED by scripts/pack-facts.ts — current pack descriptors and explicit world-runtime server backings; no prior-artifact entries are retained.",
3
- "packs": {
4
- "sentry": {
5
- "adoption": {
6
- "sdks": [
7
- "@sentry/astro",
8
- "@sentry/browser",
9
- "@sentry/electron",
10
- "@sentry/nextjs",
11
- "@sentry/node",
12
- "@sentry/nestjs",
13
- "@sentry/react",
14
- "@sentry/react-native",
15
- "@sentry/remix",
16
- "@sentry/sveltekit",
17
- "@sentry/vue",
18
- "@sentry/cli"
19
- ],
20
- "pypi": [
21
- "sentry-sdk"
22
- ],
23
- "envStems": [
24
- "APPSMITHSENTRY",
25
- "SENTRY"
26
- ]
27
- },
28
- "hosts": [
29
- {
30
- "host": "sentry.io"
31
- },
32
- {
33
- "host": "us.sentry.io"
34
- },
35
- {
36
- "host": "de.sentry.io"
37
- },
38
- {
39
- "suffix": ".ingest.sentry.io"
40
- },
41
- {
42
- "suffix": ".ingest.us.sentry.io"
43
- },
44
- {
45
- "suffix": ".ingest.de.sentry.io"
46
- }
47
- ],
48
- "archetype": "crud",
49
- "protocol": {
50
- "declared": "3",
51
- "major": 3,
52
- "standing": "current"
53
- },
54
- "transport": "rest",
55
- "resources": [
56
- "organization",
57
- "project",
58
- "project_key",
59
- "event",
60
- "issue",
61
- "release",
62
- "monitor",
63
- "check_in"
64
- ],
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
- "credentialDoor": {
67
- "path": "/_twin/app-credentials",
68
- "body": {},
69
- "fill": {
70
- "SENTRY_DSN": "dsn",
71
- "NEXT_PUBLIC_SENTRY_DSN": "dsn",
72
- "NEXT_PUBLIC_SENTRY_DSN_CLIENT": "dsn",
73
- "SENTRY_FRONT_DSN": "dsn",
74
- "SENTRY_DSN_API": "dsn",
75
- "VITE_SENTRY_DSN": "dsn",
76
- "EXPO_PUBLIC_SENTRY_DSN": "dsn",
77
- "SENTRY_AUTH_TOKEN": "auth_token",
78
- "SENTRY_ORG": "org",
79
- "SENTRY_ORGANIZATION": "org",
80
- "SENTRY_PROJECT": "project"
81
- }
82
- },
83
- "serveExport": "createSentryTwinServer"
84
- }
85
- },
86
- "undeclared": []
87
- }