@atproto/pds 0.4.68 → 0.4.70

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/api/app/bsky/feed/getPostThread.d.ts.map +1 -1
  3. package/dist/api/app/bsky/feed/getPostThread.js +11 -6
  4. package/dist/api/app/bsky/feed/getPostThread.js.map +1 -1
  5. package/dist/config/config.d.ts +1 -0
  6. package/dist/config/config.d.ts.map +1 -1
  7. package/dist/config/config.js +3 -0
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/config/env.d.ts +1 -0
  10. package/dist/config/env.d.ts.map +1 -1
  11. package/dist/config/env.js +1 -0
  12. package/dist/config/env.js.map +1 -1
  13. package/dist/context.d.ts +2 -2
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/context.js +8 -1
  16. package/dist/context.js.map +1 -1
  17. package/dist/lexicon/index.d.ts +13 -0
  18. package/dist/lexicon/index.d.ts.map +1 -1
  19. package/dist/lexicon/index.js +36 -1
  20. package/dist/lexicon/index.js.map +1 -1
  21. package/dist/lexicon/lexicons.d.ts +396 -0
  22. package/dist/lexicon/lexicons.d.ts.map +1 -1
  23. package/dist/lexicon/lexicons.js +439 -3
  24. package/dist/lexicon/lexicons.js.map +1 -1
  25. package/dist/lexicon/types/app/bsky/unspecced/getConfig.d.ts +34 -0
  26. package/dist/lexicon/types/app/bsky/unspecced/getConfig.d.ts.map +1 -0
  27. package/dist/lexicon/types/app/bsky/unspecced/getConfig.js +3 -0
  28. package/dist/lexicon/types/app/bsky/unspecced/getConfig.js.map +1 -0
  29. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +60 -2
  30. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  31. package/dist/lexicon/types/tools/ozone/moderation/defs.js +50 -0
  32. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  33. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
  34. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  35. package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +10 -0
  36. package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
  37. package/dist/lexicon/types/tools/ozone/setting/defs.d.ts +20 -0
  38. package/dist/lexicon/types/tools/ozone/setting/defs.d.ts.map +1 -0
  39. package/dist/lexicon/types/tools/ozone/setting/defs.js +15 -0
  40. package/dist/lexicon/types/tools/ozone/setting/defs.js.map +1 -0
  41. package/dist/lexicon/types/tools/ozone/setting/listOptions.d.ts +43 -0
  42. package/dist/lexicon/types/tools/ozone/setting/listOptions.d.ts.map +1 -0
  43. package/dist/lexicon/types/tools/ozone/setting/listOptions.js +3 -0
  44. package/dist/lexicon/types/tools/ozone/setting/listOptions.js.map +1 -0
  45. package/dist/lexicon/types/tools/ozone/setting/removeOptions.d.ts +40 -0
  46. package/dist/lexicon/types/tools/ozone/setting/removeOptions.d.ts.map +1 -0
  47. package/dist/lexicon/types/tools/ozone/setting/removeOptions.js +3 -0
  48. package/dist/lexicon/types/tools/ozone/setting/removeOptions.js.map +1 -0
  49. package/dist/lexicon/types/tools/ozone/setting/upsertOption.d.ts +45 -0
  50. package/dist/lexicon/types/tools/ozone/setting/upsertOption.d.ts.map +1 -0
  51. package/dist/lexicon/types/tools/ozone/setting/upsertOption.js +3 -0
  52. package/dist/lexicon/types/tools/ozone/setting/upsertOption.js.map +1 -0
  53. package/dist/pipethrough.d.ts +0 -1
  54. package/dist/pipethrough.d.ts.map +1 -1
  55. package/dist/pipethrough.js +11 -10
  56. package/dist/pipethrough.js.map +1 -1
  57. package/example.env +1 -1
  58. package/package.json +11 -11
  59. package/src/api/app/bsky/feed/getPostThread.ts +15 -4
  60. package/src/config/config.ts +5 -0
  61. package/src/config/env.ts +2 -0
  62. package/src/context.ts +11 -3
  63. package/src/lexicon/index.ts +58 -0
  64. package/src/lexicon/lexicons.ts +449 -3
  65. package/src/lexicon/types/app/bsky/unspecced/getConfig.ts +43 -0
  66. package/src/lexicon/types/tools/ozone/moderation/defs.ts +132 -0
  67. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +3 -0
  68. package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +10 -0
  69. package/src/lexicon/types/tools/ozone/setting/defs.ts +37 -0
  70. package/src/lexicon/types/tools/ozone/setting/listOptions.ts +53 -0
  71. package/src/lexicon/types/tools/ozone/setting/removeOptions.ts +49 -0
  72. package/src/lexicon/types/tools/ozone/setting/upsertOption.ts +58 -0
  73. package/src/pipethrough.ts +13 -12
  74. package/tests/proxied/__snapshots__/admin.test.ts.snap +12 -0
  75. package/tests/proxied/proxy-catchall.test.ts +1 -1
  76. package/tests/proxied/read-after-write.test.ts +20 -1
  77. package/tsconfig.build.tsbuildinfo +1 -1
@@ -30,6 +30,9 @@ export interface ModEventView {
30
30
  | ModEventResolveAppeal
31
31
  | ModEventDivert
32
32
  | ModEventTag
33
+ | AccountEvent
34
+ | IdentityEvent
35
+ | RecordEvent
33
36
  | { $type: string; [k: string]: unknown }
34
37
  subject:
35
38
  | ComAtprotoAdminDefs.RepoRef
@@ -74,6 +77,9 @@ export interface ModEventViewDetail {
74
77
  | ModEventResolveAppeal
75
78
  | ModEventDivert
76
79
  | ModEventTag
80
+ | AccountEvent
81
+ | IdentityEvent
82
+ | RecordEvent
77
83
  | { $type: string; [k: string]: unknown }
78
84
  subject:
79
85
  | RepoView
@@ -105,6 +111,10 @@ export interface SubjectStatusView {
105
111
  | ComAtprotoAdminDefs.RepoRef
106
112
  | ComAtprotoRepoStrongRef.Main
107
113
  | { $type: string; [k: string]: unknown }
114
+ hosting?:
115
+ | AccountHosting
116
+ | RecordHosting
117
+ | { $type: string; [k: string]: unknown }
108
118
  subjectBlobCids?: string[]
109
119
  subjectRepoHandle?: string
110
120
  /** Timestamp referencing when the last update was made to the moderation status of the subject */
@@ -472,6 +482,78 @@ export function validateModEventTag(v: unknown): ValidationResult {
472
482
  return lexicons.validate('tools.ozone.moderation.defs#modEventTag', v)
473
483
  }
474
484
 
485
+ /** Logs account status related events on a repo subject. Normally captured by automod from the firehose and emitted to ozone for historical tracking. */
486
+ export interface AccountEvent {
487
+ comment?: string
488
+ /** Indicates that the account has a repository which can be fetched from the host that emitted this event. */
489
+ active: boolean
490
+ status?:
491
+ | 'unknown'
492
+ | 'deactivated'
493
+ | 'deleted'
494
+ | 'takendown'
495
+ | 'suspended'
496
+ | 'tombstoned'
497
+ | (string & {})
498
+ timestamp: string
499
+ [k: string]: unknown
500
+ }
501
+
502
+ export function isAccountEvent(v: unknown): v is AccountEvent {
503
+ return (
504
+ isObj(v) &&
505
+ hasProp(v, '$type') &&
506
+ v.$type === 'tools.ozone.moderation.defs#accountEvent'
507
+ )
508
+ }
509
+
510
+ export function validateAccountEvent(v: unknown): ValidationResult {
511
+ return lexicons.validate('tools.ozone.moderation.defs#accountEvent', v)
512
+ }
513
+
514
+ /** Logs identity related events on a repo subject. Normally captured by automod from the firehose and emitted to ozone for historical tracking. */
515
+ export interface IdentityEvent {
516
+ comment?: string
517
+ handle?: string
518
+ pdsHost?: string
519
+ tombstone?: boolean
520
+ timestamp: string
521
+ [k: string]: unknown
522
+ }
523
+
524
+ export function isIdentityEvent(v: unknown): v is IdentityEvent {
525
+ return (
526
+ isObj(v) &&
527
+ hasProp(v, '$type') &&
528
+ v.$type === 'tools.ozone.moderation.defs#identityEvent'
529
+ )
530
+ }
531
+
532
+ export function validateIdentityEvent(v: unknown): ValidationResult {
533
+ return lexicons.validate('tools.ozone.moderation.defs#identityEvent', v)
534
+ }
535
+
536
+ /** Logs lifecycle event on a record subject. Normally captured by automod from the firehose and emitted to ozone for historical tracking. */
537
+ export interface RecordEvent {
538
+ comment?: string
539
+ op: 'create' | 'update' | 'delete' | (string & {})
540
+ cid?: string
541
+ timestamp: string
542
+ [k: string]: unknown
543
+ }
544
+
545
+ export function isRecordEvent(v: unknown): v is RecordEvent {
546
+ return (
547
+ isObj(v) &&
548
+ hasProp(v, '$type') &&
549
+ v.$type === 'tools.ozone.moderation.defs#recordEvent'
550
+ )
551
+ }
552
+
553
+ export function validateRecordEvent(v: unknown): ValidationResult {
554
+ return lexicons.validate('tools.ozone.moderation.defs#recordEvent', v)
555
+ }
556
+
475
557
  export interface RepoView {
476
558
  did: string
477
559
  handle: string
@@ -483,6 +565,7 @@ export interface RepoView {
483
565
  invitesDisabled?: boolean
484
566
  inviteNote?: string
485
567
  deactivatedAt?: string
568
+ threatSignatures?: ComAtprotoAdminDefs.ThreatSignature[]
486
569
  [k: string]: unknown
487
570
  }
488
571
 
@@ -512,6 +595,7 @@ export interface RepoViewDetail {
512
595
  inviteNote?: string
513
596
  emailConfirmedAt?: string
514
597
  deactivatedAt?: string
598
+ threatSignatures?: ComAtprotoAdminDefs.ThreatSignature[]
515
599
  [k: string]: unknown
516
600
  }
517
601
 
@@ -703,3 +787,51 @@ export function isVideoDetails(v: unknown): v is VideoDetails {
703
787
  export function validateVideoDetails(v: unknown): ValidationResult {
704
788
  return lexicons.validate('tools.ozone.moderation.defs#videoDetails', v)
705
789
  }
790
+
791
+ export interface AccountHosting {
792
+ status:
793
+ | 'takendown'
794
+ | 'suspended'
795
+ | 'deleted'
796
+ | 'deactivated'
797
+ | 'unknown'
798
+ | (string & {})
799
+ updatedAt?: string
800
+ createdAt?: string
801
+ deletedAt?: string
802
+ deactivatedAt?: string
803
+ reactivatedAt?: string
804
+ [k: string]: unknown
805
+ }
806
+
807
+ export function isAccountHosting(v: unknown): v is AccountHosting {
808
+ return (
809
+ isObj(v) &&
810
+ hasProp(v, '$type') &&
811
+ v.$type === 'tools.ozone.moderation.defs#accountHosting'
812
+ )
813
+ }
814
+
815
+ export function validateAccountHosting(v: unknown): ValidationResult {
816
+ return lexicons.validate('tools.ozone.moderation.defs#accountHosting', v)
817
+ }
818
+
819
+ export interface RecordHosting {
820
+ status: 'deleted' | 'unknown' | (string & {})
821
+ updatedAt?: string
822
+ createdAt?: string
823
+ deletedAt?: string
824
+ [k: string]: unknown
825
+ }
826
+
827
+ export function isRecordHosting(v: unknown): v is RecordHosting {
828
+ return (
829
+ isObj(v) &&
830
+ hasProp(v, '$type') &&
831
+ v.$type === 'tools.ozone.moderation.defs#recordHosting'
832
+ )
833
+ }
834
+
835
+ export function validateRecordHosting(v: unknown): ValidationResult {
836
+ return lexicons.validate('tools.ozone.moderation.defs#recordHosting', v)
837
+ }
@@ -29,6 +29,9 @@ export interface InputSchema {
29
29
  | ToolsOzoneModerationDefs.ModEventResolveAppeal
30
30
  | ToolsOzoneModerationDefs.ModEventEmail
31
31
  | ToolsOzoneModerationDefs.ModEventTag
32
+ | ToolsOzoneModerationDefs.AccountEvent
33
+ | ToolsOzoneModerationDefs.IdentityEvent
34
+ | ToolsOzoneModerationDefs.RecordEvent
32
35
  | { $type: string; [k: string]: unknown }
33
36
  subject:
34
37
  | ComAtprotoAdminDefs.RepoRef
@@ -22,6 +22,16 @@ export interface QueryParams {
22
22
  reportedBefore?: string
23
23
  /** Search subjects reviewed after a given timestamp */
24
24
  reviewedAfter?: string
25
+ /** Search subjects where the associated record/account was deleted after a given timestamp */
26
+ hostingDeletedAfter?: string
27
+ /** Search subjects where the associated record/account was deleted before a given timestamp */
28
+ hostingDeletedBefore?: string
29
+ /** Search subjects where the associated record/account was updated after a given timestamp */
30
+ hostingUpdatedAfter?: string
31
+ /** Search subjects where the associated record/account was updated before a given timestamp */
32
+ hostingUpdatedBefore?: string
33
+ /** Search subjects by the status of the associated record/account */
34
+ hostingStatuses?: string[]
25
35
  /** Search subjects reviewed before a given timestamp */
26
36
  reviewedBefore?: string
27
37
  /** By default, we don't include muted subjects in the results. Set this to true to include them. */
@@ -0,0 +1,37 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { lexicons } from '../../../../lexicons'
6
+ import { isObj, hasProp } from '../../../../util'
7
+ import { CID } from 'multiformats/cid'
8
+
9
+ export interface Option {
10
+ key: string
11
+ did: string
12
+ value: {}
13
+ description?: string
14
+ createdAt?: string
15
+ updatedAt?: string
16
+ managerRole?:
17
+ | 'tools.ozone.team.defs#roleModerator'
18
+ | 'tools.ozone.team.defs#roleTriage'
19
+ | 'tools.ozone.team.defs#roleAdmin'
20
+ | (string & {})
21
+ scope: 'instance' | 'personal' | (string & {})
22
+ createdBy: string
23
+ lastUpdatedBy: string
24
+ [k: string]: unknown
25
+ }
26
+
27
+ export function isOption(v: unknown): v is Option {
28
+ return (
29
+ isObj(v) &&
30
+ hasProp(v, '$type') &&
31
+ v.$type === 'tools.ozone.setting.defs#option'
32
+ )
33
+ }
34
+
35
+ export function validateOption(v: unknown): ValidationResult {
36
+ return lexicons.validate('tools.ozone.setting.defs#option', v)
37
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { lexicons } from '../../../../lexicons'
7
+ import { isObj, hasProp } from '../../../../util'
8
+ import { CID } from 'multiformats/cid'
9
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+ import * as ToolsOzoneSettingDefs from './defs'
11
+
12
+ export interface QueryParams {
13
+ limit: number
14
+ cursor?: string
15
+ scope: 'instance' | 'personal' | (string & {})
16
+ /** Filter keys by prefix */
17
+ prefix?: string
18
+ /** Filter for only the specified keys. Ignored if prefix is provided */
19
+ keys?: string[]
20
+ }
21
+
22
+ export type InputSchema = undefined
23
+
24
+ export interface OutputSchema {
25
+ cursor?: string
26
+ options: ToolsOzoneSettingDefs.Option[]
27
+ [k: string]: unknown
28
+ }
29
+
30
+ export type HandlerInput = undefined
31
+
32
+ export interface HandlerSuccess {
33
+ encoding: 'application/json'
34
+ body: OutputSchema
35
+ headers?: { [key: string]: string }
36
+ }
37
+
38
+ export interface HandlerError {
39
+ status: number
40
+ message?: string
41
+ }
42
+
43
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
44
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
45
+ auth: HA
46
+ params: QueryParams
47
+ input: HandlerInput
48
+ req: express.Request
49
+ res: express.Response
50
+ }
51
+ export type Handler<HA extends HandlerAuth = never> = (
52
+ ctx: HandlerReqCtx<HA>,
53
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,49 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { lexicons } from '../../../../lexicons'
7
+ import { isObj, hasProp } from '../../../../util'
8
+ import { CID } from 'multiformats/cid'
9
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+ export interface QueryParams {}
12
+
13
+ export interface InputSchema {
14
+ keys: string[]
15
+ scope: 'instance' | 'personal' | (string & {})
16
+ [k: string]: unknown
17
+ }
18
+
19
+ export interface OutputSchema {
20
+ [k: string]: unknown
21
+ }
22
+
23
+ export interface HandlerInput {
24
+ encoding: 'application/json'
25
+ body: InputSchema
26
+ }
27
+
28
+ export interface HandlerSuccess {
29
+ encoding: 'application/json'
30
+ body: OutputSchema
31
+ headers?: { [key: string]: string }
32
+ }
33
+
34
+ export interface HandlerError {
35
+ status: number
36
+ message?: string
37
+ }
38
+
39
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
40
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
+ auth: HA
42
+ params: QueryParams
43
+ input: HandlerInput
44
+ req: express.Request
45
+ res: express.Response
46
+ }
47
+ export type Handler<HA extends HandlerAuth = never> = (
48
+ ctx: HandlerReqCtx<HA>,
49
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,58 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { lexicons } from '../../../../lexicons'
7
+ import { isObj, hasProp } from '../../../../util'
8
+ import { CID } from 'multiformats/cid'
9
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+ import * as ToolsOzoneSettingDefs from './defs'
11
+
12
+ export interface QueryParams {}
13
+
14
+ export interface InputSchema {
15
+ key: string
16
+ scope: 'instance' | 'personal' | (string & {})
17
+ value: {}
18
+ description?: string
19
+ managerRole?:
20
+ | 'tools.ozone.team.defs#roleModerator'
21
+ | 'tools.ozone.team.defs#roleTriage'
22
+ | 'tools.ozone.team.defs#roleAdmin'
23
+ | (string & {})
24
+ [k: string]: unknown
25
+ }
26
+
27
+ export interface OutputSchema {
28
+ option: ToolsOzoneSettingDefs.Option
29
+ [k: string]: unknown
30
+ }
31
+
32
+ export interface HandlerInput {
33
+ encoding: 'application/json'
34
+ body: InputSchema
35
+ }
36
+
37
+ export interface HandlerSuccess {
38
+ encoding: 'application/json'
39
+ body: OutputSchema
40
+ headers?: { [key: string]: string }
41
+ }
42
+
43
+ export interface HandlerError {
44
+ status: number
45
+ message?: string
46
+ }
47
+
48
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
49
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
50
+ auth: HA
51
+ params: QueryParams
52
+ input: HandlerInput
53
+ req: express.Request
54
+ res: express.Response
55
+ }
56
+ export type Handler<HA extends HandlerAuth = never> = (
57
+ ctx: HandlerReqCtx<HA>,
58
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -357,9 +357,9 @@ async function pipethroughRequest(
357
357
 
358
358
  function handleUpstreamRequestError(
359
359
  err: unknown,
360
- message = 'pipethrough network error',
360
+ message = 'Upstream service unreachable',
361
361
  ): never {
362
- httpLogger.warn({ err }, message)
362
+ httpLogger.error({ err }, message)
363
363
  throw new XRPCServerError(ResponseType.UpstreamFailure, message, undefined, {
364
364
  cause: err,
365
365
  })
@@ -520,18 +520,11 @@ async function tryParsingError(
520
520
  }
521
521
  }
522
522
 
523
- export async function bufferUpstreamResponse(
523
+ async function bufferUpstreamResponse(
524
524
  readable: Readable,
525
525
  contentEncoding?: string | string[],
526
526
  ): Promise<Buffer> {
527
527
  try {
528
- // Needed for type-safety (should never happen irl)
529
- if (Array.isArray(contentEncoding)) {
530
- throw new TypeError(
531
- 'upstream service returned multiple content-encoding headers',
532
- )
533
- }
534
-
535
528
  return await streamToNodeBuffer(decodeStream(readable, contentEncoding))
536
529
  } catch (err) {
537
530
  if (!readable.destroyed) readable.destroy()
@@ -561,7 +554,11 @@ export async function asPipeThroughBuffer(
561
554
  // Response parsing/forwarding
562
555
  // -------------------
563
556
 
564
- const RES_HEADERS_TO_FORWARD = ['atproto-repo-rev', 'atproto-content-labelers']
557
+ const RES_HEADERS_TO_FORWARD = [
558
+ 'atproto-repo-rev',
559
+ 'atproto-content-labelers',
560
+ 'retry-after',
561
+ ]
565
562
 
566
563
  function* responseHeaders(
567
564
  headers: IncomingHttpHeaders,
@@ -584,7 +581,11 @@ function* responseHeaders(
584
581
  for (let i = 0; i < RES_HEADERS_TO_FORWARD.length; i++) {
585
582
  const name = RES_HEADERS_TO_FORWARD[i]
586
583
  const val = headers[name]
587
- if (typeof val === 'string') yield [name, val]
584
+
585
+ if (val != null) {
586
+ const value: string = Array.isArray(val) ? val.join(',') : val
587
+ yield [name, value]
588
+ }
588
589
  }
589
590
  }
590
591
 
@@ -122,6 +122,10 @@ Object {
122
122
  "moderation": Object {
123
123
  "subjectStatus": Object {
124
124
  "createdAt": "1970-01-01T00:00:00.000Z",
125
+ "hosting": Object {
126
+ "$type": "tools.ozone.moderation.defs#accountHosting",
127
+ "status": "unknown",
128
+ },
125
129
  "id": 1,
126
130
  "lastReportedAt": "1970-01-01T00:00:00.000Z",
127
131
  "lastReviewedAt": "1970-01-01T00:00:00.000Z",
@@ -214,6 +218,10 @@ Object {
214
218
  "moderation": Object {
215
219
  "subjectStatus": Object {
216
220
  "createdAt": "1970-01-01T00:00:00.000Z",
221
+ "hosting": Object {
222
+ "$type": "tools.ozone.moderation.defs#recordHosting",
223
+ "status": "unknown",
224
+ },
217
225
  "id": 4,
218
226
  "lastReviewedAt": "1970-01-01T00:00:00.000Z",
219
227
  "lastReviewedBy": "user(1)",
@@ -271,6 +279,10 @@ Object {
271
279
  "moderation": Object {
272
280
  "subjectStatus": Object {
273
281
  "createdAt": "1970-01-01T00:00:00.000Z",
282
+ "hosting": Object {
283
+ "$type": "tools.ozone.moderation.defs#accountHosting",
284
+ "status": "unknown",
285
+ },
274
286
  "id": 1,
275
287
  "lastReportedAt": "1970-01-01T00:00:00.000Z",
276
288
  "lastReviewedAt": "1970-01-01T00:00:00.000Z",
@@ -116,7 +116,7 @@ describe('proxy header', () => {
116
116
  for (const lex of lexicons) client.lex.add(lex)
117
117
 
118
118
  await expect(client.call('com.example.ok')).rejects.toThrow(
119
- 'pipethrough network error',
119
+ 'Upstream service unreachable',
120
120
  )
121
121
  })
122
122
 
@@ -101,12 +101,22 @@ describe('proxy read after write', () => {
101
101
  { uri: sc.posts[alice][0].ref.uriStr },
102
102
  { headers: { ...sc.getHeaders(alice) } },
103
103
  )
104
- const layerOne = res.data.thread.replies as ThreadViewPost[]
104
+ const thread = res.data.thread as ThreadViewPost
105
+ const layerOne = thread.replies as ThreadViewPost[]
105
106
  expect(layerOne.length).toBe(1)
106
107
  expect(layerOne[0].post.uri).toEqual(reply1.ref.uriStr)
107
108
  const layerTwo = layerOne[0].replies as ThreadViewPost[]
108
109
  expect(layerTwo.length).toBe(1)
109
110
  expect(layerTwo[0].post.uri).toEqual(reply2.ref.uriStr)
111
+
112
+ const aliceHandle = sc.accounts[alice].handle
113
+ const handleUriStr = thread.post.uri.replace(alice, aliceHandle)
114
+ expect(handleUriStr).not.toEqual(thread.post.uri)
115
+ const handleRes = await agent.api.app.bsky.feed.getPostThread(
116
+ { uri: handleUriStr },
117
+ { headers: { ...sc.getHeaders(alice) } },
118
+ )
119
+ expect(handleRes.data.thread).toEqual(res.data.thread)
110
120
  })
111
121
 
112
122
  it('handles read after write on a thread that is not found on appview', async () => {
@@ -123,6 +133,15 @@ describe('proxy read after write', () => {
123
133
  expect((thread.replies?.at(0) as ThreadViewPost).post.uri).toEqual(
124
134
  replyRef2.uriStr,
125
135
  )
136
+
137
+ const aliceHandle = sc.accounts[alice].handle
138
+ const handleUriStr = thread.post.uri.replace(alice, aliceHandle)
139
+ expect(handleUriStr).not.toEqual(thread.post.uri)
140
+ const handleRes = await agent.api.app.bsky.feed.getPostThread(
141
+ { uri: handleUriStr },
142
+ { headers: { ...sc.getHeaders(alice) } },
143
+ )
144
+ expect(handleRes.data.thread).toEqual(res.data.thread)
126
145
  })
127
146
 
128
147
  it('handles read after write on threads with record embeds', async () => {