feat(api): add ActivityPub schema parity proof#335
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughPR добавляет effect/Schema-валидаторы для JSON-LD/ActivityPub/ForgeFed, переводит контрактные типы на schema-derived типы, интегрирует валидацию в federation HTTP endpoints (actor/outbox/followers/following/liked), расширяет сервисы федерации для поддержки followers?page=1 и добавляет паритетные и интеграционные тесты. ChangesВалидация ActivityPub через Effect Schema
Sequence Diagram(s)sequenceDiagram
participant Client
participant HTTPHandler
participant FederationService
participant SchemaValidator
Client->>HTTPHandler: GET /federation/followers?page=1
HTTPHandler->>FederationService: makeFederationFollowersPageCollection(context)
FederationService->>SchemaValidator: produce raw page document
SchemaValidator->>HTTPHandler: validated document (ActivityPubOrderedCollectionPageSchema)
HTTPHandler->>Client: 200 application/activity+json
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 7✅ Passed checks (7 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/api/src/api/activitypub-schema.ts`:
- Around line 63-64: The ActivityPub contract uses Schema.Unknown for fields
attachment, raw and orderedItems (and other occurrences around the 132-143
block), which permits arbitrary values; replace these Schema.Unknown usages with
concrete, JSON-safe schemas that reflect the expected shape (e.g.,
Schema.Object/Schema.Record with typed properties,
Schema.Array(Schema.String|Schema.Object) or Schema.String/Schema.Number as
appropriate) so validation enforces JSON-compatible structure; update the
schemas for attachment, raw, orderedItems (and the other Schema.Unknown
instances) to specific Schema.* types matching the ActivityPub spec for those
fields and adjust any decoding/consumers to handle the stricter types.
- Line 83: ActivityPubPersonSchema currently uses the overly-broad
JsonLdContextSchema for its "`@context`" field; narrow it to an actor-specific
context by replacing the "`@context`": JsonLdContextSchema entry with a new or
existing ActorContextSchema (e.g., ActorContextSchema or
ActivityPubActorContextSchema) that only allows the expected actor/activitypub
contexts/URLs/objects; add or import the ActorContextSchema, update
ActivityPubPersonSchema's "`@context`" to reference it, and ensure the new schema
validates the actor-specific keys/URLs so invalid actor documents are rejected.
In `@packages/api/tests/activitypub-schema-parity.test.ts`:
- Around line 146-233: Add fast-check property tests that assert the decoding
invariants for ActivityPub schemas: use fc.assert + fc.property to generate
objects and verify that Schema.decodeUnknownEither(ActivityPubPersonSchema),
Schema.decodeUnknownEither(ActivityPubOrderedCollectionSchema), and
Schema.decodeUnknownEither(ActivityPubOrderedCollectionPageSchema) succeed when
the generated object has the correct literal "type" and all required fields
present, and fail when either the "type" is incorrect or required fields are
missing; use Either.isRight to assert success and Either.isLeft to assert
failure (same pattern as existing tests) and reuse the expect/validation helpers
(e.g., expectDecodedCoversFixtureKeys or simple Either checks) to keep
assertions consistent.
In `@packages/api/tests/http-config.test.ts`:
- Around line 268-282: Add a negative test next to the existing followers
happy-path: call readFederationDocumentRoute("/federation/followers?page=2")
(after clearFederationState()), parse the response with
parseJsonObject(document.body) and assert document.status is 400 and the parsed
payload contains the expected unsupported-page error message (e.g.,
readField(payload, "message") or appropriate error field equals/contains
"unsupported page"); keep content-type/assertions consistent with how other
error responses are validated in the suite and don't call decodeOrThrow for the
success schema (ActivityPubOrderedCollectionPageSchema) in this case.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b50984ac-fd72-4ea2-a995-3599bc746d60
📒 Files selected for processing (7)
packages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/api/schema.tspackages/api/src/http.tspackages/api/src/services/federation.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/tests/http-config.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: E2E (Runtime volumes + SSH)
- GitHub Check: E2E (Browser command)
- GitHub Check: E2E (Clone auto-open SSH)
- GitHub Check: E2E (Clone cache)
- GitHub Check: E2E (Login context)
- GitHub Check: E2E (OpenCode)
- GitHub Check: Lint
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Implement Functional Core, Imperative Shell (FCIS) pattern: CORE layer contains only pure functions with immutable data and mathematical operations; SHELL layer isolates all effects (IO, network, database). Strict dependency direction: SHELL → CORE (never reverse).
Never useany,unknown,eslint-disable,ts-ignore, orastype assertions (except in rigorously justified cases with documentation). Always use exhaustive union type analysis through.exhaustive()pattern matching.
All external dependencies must be wrapped through typed interfaces and injected via Effect-TS Layer pattern. Never call external services directly from CORE functions.
Use monadic composition with Effect-TS for all effects:Effect<Success, Error, Requirements>. Compose effects throughpipe()andEffect.flatMap(). Implement dependency injection via Layer pattern. Handle errors without try/catch blocks.
All functions must be pure in the CORE layer: no side effects (logging, console output, IO operations, mutations). Separate all side effects into the SHELL layer.
Use exhaustive pattern matching with Effect.Match instead of switch statements. Example:Match.value(item).pipe(Match.when(...), Match.exhaustive).
Document all functions with comprehensive TSDoc including:@pure(true/false),@effect(required services),@invariant(mathematical invariants),@precondition,@postcondition,@complexity(time and space),@throwsNever (errors must be typed in Effect).
Use functional comment markers for code clarity: CHANGE (brief description), WHY (mathematical/architectural justification), QUOTE(ТЗ) (requirement citation), REF (RTM or message ID), SOURCE (external source with quote), FORMAT THEOREM (∀x ∈ Domain: P(x) → Q(f(x))), PURITY (CORE|SHELL), EFFECT (Effect type signature), INVARIANT (mathematical invariant), COMPLEXITY (time/space).
Define all external service dependencies as Context.Tag classes with fully typed methods returning Effect types. Example: `class Da...
Files:
packages/api/src/api/schema.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/services/federation.tspackages/api/src/api/contracts.tspackages/api/tests/http-config.test.tspackages/api/src/http.tspackages/api/src/api/activitypub-schema.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Forbidden constructs in CORE code:any,eslint-disable,ts-ignore,async/await, raw Promise chains (then/catch),Promise.all,try/catchfor logic control,console.*, switch statements (use Match with .exhaustive() instead)
All functions must use Effect-TS for composing effects:Effect<Success, Error, Requirements>. No direct async/await, Promise chains, or try/catch in product logic.
Functional comments must include: CHANGE, WHY, QUOTE(ТЗ) or n/a, REF, SOURCE or n/a, FORMAT THEOREM, PURITY (CORE|SHELL), EFFECT signature for SHELL functions, INVARIANT, and COMPLEXITY.
All data mutations must use immutable patterns (ReadonlyArray, readonly properties, Object.freeze); mutation in SHELL only when absolutely necessary and documented.
Files:
packages/api/src/api/schema.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/services/federation.tspackages/api/src/api/contracts.tspackages/api/tests/http-config.test.tspackages/api/src/http.tspackages/api/src/api/activitypub-schema.ts
**/*.{sh,bash,py,js,ts,jsx,tsx,go,java,rb,php}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files introduce command injection or unsafe shell/process execution with user-controlled input
Files:
packages/api/src/api/schema.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/services/federation.tspackages/api/src/api/contracts.tspackages/api/tests/http-config.test.tspackages/api/src/http.tspackages/api/src/api/activitypub-schema.ts
**/*.{py,js,ts,jsx,tsx,go,java,rb,php,sh,bash,c,cpp}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files introduce path traversal or writes outside intended project/container state directories
Files:
packages/api/src/api/schema.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/services/federation.tspackages/api/src/api/contracts.tspackages/api/tests/http-config.test.tspackages/api/src/http.tspackages/api/src/api/activitypub-schema.ts
**/*.{js,ts,jsx,tsx,py,java,go,rb,php,sh,bash,yml,yaml,json,env*,toml,cfg,config,dockerfile,dockerignore}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files expose credentials, tokens, private-keys, or PII in source, generated config, logs, or CI output
Files:
packages/api/src/api/schema.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/services/federation.tspackages/api/src/api/contracts.tspackages/api/tests/http-config.test.tspackages/api/src/http.tspackages/api/src/api/activitypub-schema.ts
packages/api/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (README.md)
API layer should work only with LIB and serve as the intermediary between APP and LIB
Files:
packages/api/src/api/schema.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/services/federation.tspackages/api/src/api/contracts.tspackages/api/tests/http-config.test.tspackages/api/src/http.tspackages/api/src/api/activitypub-schema.ts
**/*
⚙️ CodeRabbit configuration file
**/*: Ты строгий ревьюер SPEC DRIVEN DEVELOPMENT.Перед выводами изучи README.md, другие *.md файлы, linked issues,
PR description, PR comments/discussion и релевантную кодовую базу.Сверь изменения с исходным ТЗ/спекой и обсуждением. Флагай любой уход
от спеки, недокументированное изменение поведения, отсутствие тестов
для заявленного поведения и security-риск. Если спека не видна,
попроси автора добавить ее в issue или PR description.Проверь решение с точки зрения формальной верификации: какие инварианты,
предусловия и постусловия можно доказать математически, а где доказуемость
слабая. Оцени решение с точки зрения теории игр: устойчивы ли стимулы,
нет ли выгодного обхода правил, и какое решение было бы сильнее.
Files:
packages/api/src/api/schema.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/services/federation.tspackages/api/src/api/contracts.tspackages/api/tests/http-config.test.tspackages/api/src/http.tspackages/api/src/api/activitypub-schema.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.{ts,tsx}: Implement property-based testing using fast-check for mathematical properties and invariants. Example:fc.property(fc.array(messageArbitrary), (messages) => isChronologicallySorted(sortMessagesByTimestamp(messages))).
Mock external dependencies in unit tests using Effect's testing utilities. Run tests without Effect runtime for speed. Example:Effect.provide(MockService), Effect.runPromise.
Files:
packages/api/tests/activitypub-schema-parity.test.tspackages/api/tests/http-config.test.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Property-based tests (fast-check) must verify mathematical invariants; unit tests must use Effect test utilities without async/await.
Files:
packages/api/tests/activitypub-schema-parity.test.tspackages/api/tests/http-config.test.ts
🧠 Learnings (1)
📚 Learning: 2026-05-18T07:43:38.131Z
Learnt from: skulidropek
Repo: ProverCoderAI/docker-git PR: 313
File: packages/api/src/services/panel-cloudflare-tunnel.ts:123-135
Timestamp: 2026-05-18T07:43:38.131Z
Learning: In this repo’s ProverCoderAI/docker-git architecture, it is intentional for service modules under packages/api/src/services/ to call Node.js APIs directly (e.g., fs, child_process, fetch). Do not treat direct Node API usage in these service modules as a dependency-injection (DI) violation. This codebase provides the NodeContext at the program boundary (entry point) rather than injecting Context.Tag/Layer services into individual service modules; reviewers should only flag DI/context issues if the expected boundary setup is missing.
Applied to files:
packages/api/src/services/federation.ts
🔇 Additional comments (2)
packages/api/src/api/contracts.ts (1)
573-574: LGTM!Also applies to: 576-578, 618-627
packages/api/src/api/schema.ts (1)
3-16: LGTM!
|
@coderabbitai можешь проверить на сколько код реализует Должно быть 1 в 1 |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 26933
✅ Что работает корректноРеальный Mastodon JSON успешно пройдёт декодирование через
Опциональные поля ❌ Пробелы (не покрывает реальный Mastodon JSON)1.
|
| Поле | Тип | Описание |
|---|---|---|
alsoKnownAs |
string[] |
Псевдонимы аккаунта (при переезде) |
movedTo |
string |
Ссылка на новый аккаунт |
suspended |
boolean |
Заблокирован ли аккаунт |
attributionDomains |
string[] |
Домены для attribution |
icon |
object |
Аватар (Image с url, mediaType) |
image |
object |
Шапка профиля |
devices |
string |
URL списка устройств |
3. tag и attachment — не типизированы
Сейчас это Schema.Array(Schema.Unknown). В реальных ответах:
attachment— массивPropertyValueобъектов ({ type: "PropertyValue", name: string, value: string })tag— массивHashtag/Emojiобъектов
4. interactionPolicy — слишком широкая схема
Текущая: Schema.Record({ key: Schema.String, value: JsonValueSchema }).
Реальная структура:
{
"canFeature": { "automaticApproval": ["..."] },
"canQuote": { "automaticApproval": [...], "manualApproval": [...] }
}5. Поле name — обязательное, но может отсутствовать
У сервисных аккаунтов/ботов name может отсутствовать. Рекомендуется сделать OptionalString.
Итоговая оценка
| Аспект | Статус |
|---|---|
| Все обязательные поля Person | ✅ Покрыто |
| Опциональные поля Mastodon | ✅ ~85% покрыто |
@context type-check |
❌ Не реализован |
alsoKnownAs, movedTo, suspended |
❌ Отсутствуют |
attachment / tag типизация |
|
interactionPolicy структура |
|
icon / image / devices |
❌ Отсутствуют |
Основной запрос — добавить строгую схему @context — требует создания MastodonActorJsonLdContextSchema (или обобщённого GenericActorJsonLdContextSchema), который принимает webfinger URI и типизированный объект расширений. Хотите, чтобы я запланировал эти изменения?
Guarantee schema parity with the Mastodon actor fixture and remove Schema.Unknown from the ActivityPub API boundary. Proof obligations: required ActivityPub object fields decode iff their literal type/context invariants hold; ordered collection items and ForgeFed raw payloads are JSON values; unsupported followers pages fail with typed 400 errors.
|
@coderabbitai please verify whether issue #334 is now fully implemented against the issue JSON and your review comments. In particular, please check the proof obligations in the PR body: exact actor JSON-LD context parity, no |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 178670
✅ Все требования реализованы — блокеров нет1. Exact actor JSON-LD context parity ✅
2. No
|
| Поле | Тип |
|---|---|
tag |
Schema.Array(ActivityPubActorTagSchema) |
attachment |
Schema.Array(ActivityPubActorAttachmentSchema) |
orderedItems |
Schema.Array(JsonValueSchema) |
ForgeFedTicketSchema.attachment |
Schema.Array(JsonValueSchema) |
ForgeFedTicketSchema.raw |
Schema.optional(JsonValueSchema) |
Schema.Unknown не встречается ни в одном экспортируемом ActivityPub/ForgeFed схеме.
3. Mastodon actor optional fields ✅
Все поля, ранее отсутствовавшие, теперь есть в ActivityPubPersonSchema:
alsoKnownAs, movedTo, suspended, attributionDomains, icon, image, devices, showFeatured, showMedia, showRepliesInMedia, featuredCollections, memorial, name (сделан optional).
4. Structured tag/attachment ✅
ActivityPubActorTagSchema = Struct({ type, name, href, id, icon, updated }, Record(String, JsonValue))
ActivityPubActorAttachmentSchema = Struct({ type, name, value }, Record(String, JsonValue))Оба — типизированные Struct с открытым Record для forward-compat.
5. Typed interactionPolicy ✅
MastodonInteractionPolicySchema = Struct({
canFeature: optional(ActivityPubInteractionApprovalSchema),
canQuote: optional(ActivityPubInteractionApprovalSchema)
}, Record(String, JsonValue))
ActivityPubInteractionApprovalSchema = Struct({
automaticApproval: optional(Array(String)),
manualApproval: optional(Array(String))
}, Record(String, JsonValue))Fixture interactionPolicy.canFeature.automaticApproval: [...] декодируется корректно.
6. Property-based schema invariants ✅
В activitypub-schema-parity.test.ts присутствуют fc.property тесты для всех трёх схем:
- Person: принимает с обязательными полями, отклоняет с неверным
type, отклоняет с отсутствующими полями - OrderedCollection: аналогично
- OrderedCollectionPage: аналогично
Итого 9 property-based тест-кейсов.
7. Negative /federation/followers?page=2 HTTP test ✅
В http-config.test.ts:
it.effect("rejects unsupported followers pages", () => ...)
// expect(document.status).toBe(400)
// expect(...error.type).toBe("ApiBadRequestError")
// expect(...error.message).toBe("Unsupported followers page: 2")readFollowersPageMode в http.ts возвращает Effect.fail(new ApiBadRequestError(...)) для любого page !== "1".
PR #335 полностью реализует все требования issue #334. Нет ни одного оставшегося блокера.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/api/src/api/activitypub-schema.ts (1)
119-122:⚠️ Potential issue | 🟠 Major | ⚡ Quick winСлишком широкий fallback в
interactionPolicyослабляет parity-доказательство.Сейчас
interactionPolicyна Line 189 принимает почти любой объект из-заActivityPubInteractionPolicySchema(Record<string, JsonValue>). Это пропускает структурно неверные payload и не выполняет требование “fail on mismatched literals”.Возможный минимальный фикс
-const ActivityPubInteractionPolicySchema = Schema.Record({ - key: Schema.String, - value: JsonValueSchema -}) @@ - interactionPolicy: Schema.optional(Schema.Union(MastodonInteractionPolicySchema, ActivityPubInteractionPolicySchema)), + interactionPolicy: Schema.optional(MastodonInteractionPolicySchema),Also applies to: 189-189
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/api/activitypub-schema.ts` around lines 119 - 122, The interactionPolicy acceptance is too permissive because ActivityPubInteractionPolicySchema currently allows arbitrary object shapes; tighten it so mismatched literals fail by replacing the loose Record schema with a dictionary mapping of string keys to JsonValue (e.g., use Schema.Dict(Schema.String, JsonValueSchema) or, if you know allowed keys, a Schema.Record with explicit literal keys) and update the interactionPolicy usage to expect that stricter ActivityPubInteractionPolicySchema; change the ActivityPubInteractionPolicySchema definition (and any code referencing interactionPolicy) to the narrower schema so structurally invalid payloads are rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/api/src/api/activitypub-schema.ts`:
- Around line 124-145: The nested actor schemas (ActivityPubImageSchema,
ActivityPubActorAttachmentSchema, ActivityPubActorTagSchema) are too permissive
because they allow all fields optional and an open record of extra keys, so
icon/image/tag/attachment decode succeeds without required structure; tighten
them by making "type" required (use Schema.String not OptionalString) and
replace the broad Schema.Record extra-keys allowance with explicit discriminated
unions or concrete schemas for expected types (e.g., PropertyValue, Emoji,
Hashtag or an explicit Image/Attachment/Tag schema) so each variant enforces its
required fields; update ActivityPubImageSchema,
ActivityPubActorAttachmentSchema, ActivityPubActorTagSchema accordingly (and
apply same tightening to the other occurrences mentioned at the same spots).
---
Outside diff comments:
In `@packages/api/src/api/activitypub-schema.ts`:
- Around line 119-122: The interactionPolicy acceptance is too permissive
because ActivityPubInteractionPolicySchema currently allows arbitrary object
shapes; tighten it so mismatched literals fail by replacing the loose Record
schema with a dictionary mapping of string keys to JsonValue (e.g., use
Schema.Dict(Schema.String, JsonValueSchema) or, if you know allowed keys, a
Schema.Record with explicit literal keys) and update the interactionPolicy usage
to expect that stricter ActivityPubInteractionPolicySchema; change the
ActivityPubInteractionPolicySchema definition (and any code referencing
interactionPolicy) to the narrower schema so structurally invalid payloads are
rejected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0734e014-5015-4a05-a93f-338cba998630
📒 Files selected for processing (5)
packages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/tests/http-config.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: E2E (Runtime volumes + SSH)
- GitHub Check: Test
- GitHub Check: E2E (Clone auto-open SSH)
- GitHub Check: E2E (Clone cache)
- GitHub Check: E2E (Browser command)
- GitHub Check: E2E (Login context)
- GitHub Check: E2E (OpenCode)
- GitHub Check: Lint
- GitHub Check: Final build (windows-latest)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Implement Functional Core, Imperative Shell (FCIS) pattern: CORE layer contains only pure functions with immutable data and mathematical operations; SHELL layer isolates all effects (IO, network, database). Strict dependency direction: SHELL → CORE (never reverse).
Never useany,unknown,eslint-disable,ts-ignore, orastype assertions (except in rigorously justified cases with documentation). Always use exhaustive union type analysis through.exhaustive()pattern matching.
All external dependencies must be wrapped through typed interfaces and injected via Effect-TS Layer pattern. Never call external services directly from CORE functions.
Use monadic composition with Effect-TS for all effects:Effect<Success, Error, Requirements>. Compose effects throughpipe()andEffect.flatMap(). Implement dependency injection via Layer pattern. Handle errors without try/catch blocks.
All functions must be pure in the CORE layer: no side effects (logging, console output, IO operations, mutations). Separate all side effects into the SHELL layer.
Use exhaustive pattern matching with Effect.Match instead of switch statements. Example:Match.value(item).pipe(Match.when(...), Match.exhaustive).
Document all functions with comprehensive TSDoc including:@pure(true/false),@effect(required services),@invariant(mathematical invariants),@precondition,@postcondition,@complexity(time and space),@throwsNever (errors must be typed in Effect).
Use functional comment markers for code clarity: CHANGE (brief description), WHY (mathematical/architectural justification), QUOTE(ТЗ) (requirement citation), REF (RTM or message ID), SOURCE (external source with quote), FORMAT THEOREM (∀x ∈ Domain: P(x) → Q(f(x))), PURITY (CORE|SHELL), EFFECT (Effect type signature), INVARIANT (mathematical invariant), COMPLEXITY (time/space).
Define all external service dependencies as Context.Tag classes with fully typed methods returning Effect types. Example: `class Da...
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.{ts,tsx}: Implement property-based testing using fast-check for mathematical properties and invariants. Example:fc.property(fc.array(messageArbitrary), (messages) => isChronologicallySorted(sortMessagesByTimestamp(messages))).
Mock external dependencies in unit tests using Effect's testing utilities. Run tests without Effect runtime for speed. Example:Effect.provide(MockService), Effect.runPromise.
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Forbidden constructs in CORE code:any,eslint-disable,ts-ignore,async/await, raw Promise chains (then/catch),Promise.all,try/catchfor logic control,console.*, switch statements (use Match with .exhaustive() instead)
All functions must use Effect-TS for composing effects:Effect<Success, Error, Requirements>. No direct async/await, Promise chains, or try/catch in product logic.
Functional comments must include: CHANGE, WHY, QUOTE(ТЗ) or n/a, REF, SOURCE or n/a, FORMAT THEOREM, PURITY (CORE|SHELL), EFFECT signature for SHELL functions, INVARIANT, and COMPLEXITY.
All data mutations must use immutable patterns (ReadonlyArray, readonly properties, Object.freeze); mutation in SHELL only when absolutely necessary and documented.
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Property-based tests (fast-check) must verify mathematical invariants; unit tests must use Effect test utilities without async/await.
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.ts
**/*.{sh,bash,py,js,ts,jsx,tsx,go,java,rb,php}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files introduce command injection or unsafe shell/process execution with user-controlled input
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.ts
**/*.{py,js,ts,jsx,tsx,go,java,rb,php,sh,bash,c,cpp}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files introduce path traversal or writes outside intended project/container state directories
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.ts
**/*.{js,ts,jsx,tsx,py,java,go,rb,php,sh,bash,yml,yaml,json,env*,toml,cfg,config,dockerfile,dockerignore}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files expose credentials, tokens, private-keys, or PII in source, generated config, logs, or CI output
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.ts
packages/api/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (README.md)
API layer should work only with LIB and serve as the intermediary between APP and LIB
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.ts
**/*
⚙️ CodeRabbit configuration file
**/*: Ты строгий ревьюер SPEC DRIVEN DEVELOPMENT.Перед выводами изучи README.md, другие *.md файлы, linked issues,
PR description, PR comments/discussion и релевантную кодовую базу.Сверь изменения с исходным ТЗ/спекой и обсуждением. Флагай любой уход
от спеки, недокументированное изменение поведения, отсутствие тестов
для заявленного поведения и security-риск. Если спека не видна,
попроси автора добавить ее в issue или PR description.Проверь решение с точки зрения формальной верификации: какие инварианты,
предусловия и постусловия можно доказать математически, а где доказуемость
слабая. Оцени решение с точки зрения теории игр: устойчивы ли стимулы,
нет ли выгодного обхода правил, и какое решение было бы сильнее.
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/services/federation.ts
🧠 Learnings (1)
📚 Learning: 2026-05-18T07:43:38.131Z
Learnt from: skulidropek
Repo: ProverCoderAI/docker-git PR: 313
File: packages/api/src/services/panel-cloudflare-tunnel.ts:123-135
Timestamp: 2026-05-18T07:43:38.131Z
Learning: In this repo’s ProverCoderAI/docker-git architecture, it is intentional for service modules under packages/api/src/services/ to call Node.js APIs directly (e.g., fs, child_process, fetch). Do not treat direct Node API usage in these service modules as a dependency-injection (DI) violation. This codebase provides the NodeContext at the program boundary (entry point) rather than injecting Context.Tag/Layer services into individual service modules; reviewers should only flag DI/context issues if the expected boundary setup is missing.
Applied to files:
packages/api/src/services/federation.ts
🔇 Additional comments (4)
packages/api/src/api/contracts.ts (1)
559-559: LGTM!Also applies to: 574-627
packages/api/src/services/federation.ts (1)
13-13: LGTM!Also applies to: 130-147, 620-629, 653-653, 670-670, 674-683, 725-728, 750-750, 765-765
packages/api/tests/activitypub-schema-parity.test.ts (1)
3-3: LGTM!Also applies to: 15-83, 304-419
packages/api/tests/http-config.test.ts (1)
135-138: LGTM!Also applies to: 289-299
|
https://github.com/coderabbitai можешь проверить на сколько код реализует {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://purl.archive.org/socialweb/webfinger",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","toot":"http://joinmastodon.org/ns#","featured":{"@id":"toot:featured","@type":"@id"},"featuredTags":{"@id":"toot:featuredTags","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"movedTo":{"@id":"as:movedTo","@type":"@id"},"schema":"http://schema.org/#","PropertyValue":"schema:PropertyValue","value":"schema:value","discoverable":"toot:discoverable","suspended":"toot:suspended","memorial":"toot:memorial","indexable":"toot:indexable","attributionDomains":{"@id":"toot:attributionDomains","@type":"@id"},"showFeatured":"toot:showFeatured","showMedia":"toot:showMedia","showRepliesInMedia":"toot:showRepliesInMedia","gts":"https://gotosocial.org/ns#","interactionPolicy":{"@id":"gts:interactionPolicy","@type":"@id"},"canQuote":{"@id":"gts:canQuote","@type":"@id"},"automaticApproval":{"@id":"gts:automaticApproval","@type":"@id"},"manualApproval":{"@id":"gts:manualApproval","@type":"@id"}}],"id":"https://mastodon.social/users/GordonFreeman","webfinger":"GordonFreeman@mastodon.social","type":"Person","following":"https://mastodon.social/users/GordonFreeman/following","followers":"https://mastodon.social/users/GordonFreeman/followers","inbox":"https://mastodon.social/users/GordonFreeman/inbox","outbox":"https://mastodon.social/users/GordonFreeman/outbox","featured":"https://mastodon.social/users/GordonFreeman/collections/featured","featuredTags":"https://mastodon.social/users/GordonFreeman/collections/tags","preferredUsername":"GordonFreeman","name":"GordonFreeman","summary":"","url":"https://mastodon.social/@GordonFreeman","manuallyApprovesFollowers":false,"discoverable":false,"indexable":false,"published":"2022-05-11T00:00:00Z","memorial":false,"showFeatured":true,"showMedia":true,"showRepliesInMedia":true,"interactionPolicy":{"canFeature":{"automaticApproval":["https://mastodon.social/users/GordonFreeman"]}},"featuredCollections":"https://mastodon.social/ap/users/108283196203417442/featured_collections","publicKey":{"id":"https://mastodon.social/users/GordonFreeman#main-key","owner":"https://mastodon.social/users/GordonFreeman","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zWlvp7jHBsNV/3QkBnu\nZpbtPWetfBFJsK7huV3ZLsUk+oeyAPQKO4KCiZiBZm3qVBCr6O7p75GAesXRpMVN\ngkrgzFCI4ZME0x/6VrVNvo4q8xpRxu+rAsULXa1mI9XiUDDcFTIJn8wW09uGa4XK\nW0KGwe2+F6q7OVJfkiAXbwRrjBg6Stn8NOd6n/TYFw4UnyMNZ6BPqnxxhEv1GsFn\n6viF/jrT6SpIY0K6kBlMk5AepFj/5LpoiTdH7bfpja3jbGQNF/h1PJ0U1kP4JvNT\nNOnWMS+5fMovjftxPwwqggB8FrHeDF1QOk8ETznugukqhtNpLTx28P+sEAJC8eZS\nPwIDAQAB\n-----END PUBLIC KEY-----\n"},"tag":[],"attachment":[],"endpoints":{"sharedInbox":"https://mastodon.social/inbox"}} Skuli Dropek, [19.05.2026 22:04] Skuli Dropek, [19.05.2026 22:06] Skuli Dropek, [19.05.2026 21:43] |
|
@coderabbitai можешь проверить на сколько код реализует {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://purl.archive.org/socialweb/webfinger",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","toot":"http://joinmastodon.org/ns#","featured":{"@id":"toot:featured","@type":"@id"},"featuredTags":{"@id":"toot:featuredTags","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"movedTo":{"@id":"as:movedTo","@type":"@id"},"schema":"http://schema.org/#","PropertyValue":"schema:PropertyValue","value":"schema:value","discoverable":"toot:discoverable","suspended":"toot:suspended","memorial":"toot:memorial","indexable":"toot:indexable","attributionDomains":{"@id":"toot:attributionDomains","@type":"@id"},"showFeatured":"toot:showFeatured","showMedia":"toot:showMedia","showRepliesInMedia":"toot:showRepliesInMedia","gts":"https://gotosocial.org/ns#","interactionPolicy":{"@id":"gts:interactionPolicy","@type":"@id"},"canQuote":{"@id":"gts:canQuote","@type":"@id"},"automaticApproval":{"@id":"gts:automaticApproval","@type":"@id"},"manualApproval":{"@id":"gts:manualApproval","@type":"@id"}}],"id":"https://mastodon.social/users/GordonFreeman","webfinger":"GordonFreeman@mastodon.social","type":"Person","following":"https://mastodon.social/users/GordonFreeman/following","followers":"https://mastodon.social/users/GordonFreeman/followers","inbox":"https://mastodon.social/users/GordonFreeman/inbox","outbox":"https://mastodon.social/users/GordonFreeman/outbox","featured":"https://mastodon.social/users/GordonFreeman/collections/featured","featuredTags":"https://mastodon.social/users/GordonFreeman/collections/tags","preferredUsername":"GordonFreeman","name":"GordonFreeman","summary":"","url":"https://mastodon.social/@GordonFreeman","manuallyApprovesFollowers":false,"discoverable":false,"indexable":false,"published":"2022-05-11T00:00:00Z","memorial":false,"showFeatured":true,"showMedia":true,"showRepliesInMedia":true,"interactionPolicy":{"canFeature":{"automaticApproval":["https://mastodon.social/users/GordonFreeman"]}},"featuredCollections":"https://mastodon.social/ap/users/108283196203417442/featured_collections","publicKey":{"id":"https://mastodon.social/users/GordonFreeman#main-key","owner":"https://mastodon.social/users/GordonFreeman","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zWlvp7jHBsNV/3QkBnu\nZpbtPWetfBFJsK7huV3ZLsUk+oeyAPQKO4KCiZiBZm3qVBCr6O7p75GAesXRpMVN\ngkrgzFCI4ZME0x/6VrVNvo4q8xpRxu+rAsULXa1mI9XiUDDcFTIJn8wW09uGa4XK\nW0KGwe2+F6q7OVJfkiAXbwRrjBg6Stn8NOd6n/TYFw4UnyMNZ6BPqnxxhEv1GsFn\n6viF/jrT6SpIY0K6kBlMk5AepFj/5LpoiTdH7bfpja3jbGQNF/h1PJ0U1kP4JvNT\nNOnWMS+5fMovjftxPwwqggB8FrHeDF1QOk8ETznugukqhtNpLTx28P+sEAJC8eZS\nPwIDAQAB\n-----END PUBLIC KEY-----\n"},"tag":[],"attachment":[],"endpoints":{"sharedInbox":"https://mastodon.social/inbox"}} Skuli Dropek, [19.05.2026 22:04] Skuli Dropek, [19.05.2026 22:06] Skuli Dropek, [19.05.2026 21:43] |
Close the CodeRabbit proof gap by replacing permissive actor extension records with literal discriminated schemas for Image, PropertyValue, Hashtag, and Emoji. The interactionPolicy contract now accepts only Mastodon canFeature/canQuote approval blocks with at least one approval list, and regression tests reject the prior empty/arbitrary payload counterexamples.
|
@coderabbitai please verify whether pull request review 4334448958 is now fully implemented. I addressed the two proof gaps by replacing permissive actor extension schemas with literal variants ( |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 48901
✅ Gap 1 — Literal variants for
|
| Schema | Required fields enforced | Literal type |
|---|---|---|
ActivityPubImageSchema |
type, url |
"Image" ✅ |
ActivityPubActorAttachmentSchema |
type, name, value |
"PropertyValue" ✅ |
ActivityPubHashtagTagSchema |
type, name, href |
"Hashtag" ✅ |
ActivityPubEmojiTagSchema |
type, id, name, icon |
"Emoji" ✅ |
ActivityPubActorTagSchema |
Discriminated Union(Hashtag, Emoji) |
✅ |
✅ Gap 2 — interactionPolicy fallback removed, non-empty blocks required
ActivityPubInteractionApprovalSchemausesSchema.filter— rejects unlessautomaticApprovalormanualApprovalis present.MastodonInteractionPolicySchemausesSchema.filter— rejects unlesscanFeatureorcanQuoteis present.ActivityPubPersonSchema.interactionPolicyis nowSchema.optional(MastodonInteractionPolicySchema)with no loose Record fallback.
✅ Regression tests for exact counterexamples
The test "rejects structurally invalid ActivityPub Person actor extensions" covers all 9 counterexamples:
icon: {},image: { type: "Image" }(nourl) → rejectedtag: [{}],tag: [Emoji without icon]→ rejectedattachment: [{}],attachment: [{ type: "Note", ... }]→ rejectedinteractionPolicy: {},interactionPolicy: { arbitrary: true },interactionPolicy: { canFeature: { arbitrary: true } }→ rejected
Final status
| Check | Status |
|---|---|
| Schema.Unknown eliminated | ✅ |
@context actor-specific (ActorJsonLdContextSchema) |
✅ |
| Mastodon optional fields (alsoKnownAs, icon, image, etc.) | ✅ |
| Literal variants for Image/PropertyValue/Hashtag/Emoji | ✅ |
| interactionPolicy fallback removed, non-empty filter | ✅ |
| Property-based schema invariant tests (fast-check, 9 × 3) | ✅ |
Negative /federation/followers?page=2 HTTP test (400) |
✅ |
| Mastodon actor fixture parity test | ✅ |
PR #335 fully satisfies issue #334 and review 4334448958. No remaining blockers.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 7667 🏁 Script executed: Length of output: 2092
✅
|
| Ключ фикстуры | Тип в фикстуре | Схема | Статус |
|---|---|---|---|
manuallyApprovesFollowers |
"as:..." (string) |
OptionalString |
✅ |
toot |
"http://..." (string) |
OptionalString |
✅ |
featured |
{"@id":..., "@type":...} |
JsonLdIdMappingSchema |
✅ |
featuredTags |
{"@id":..., "@type":...} |
JsonLdIdMappingSchema |
✅ |
alsoKnownAs |
{"@id":..., "@type":...} |
JsonLdIdMappingSchema |
✅ |
movedTo |
{"@id":..., "@type":...} |
JsonLdIdMappingSchema |
✅ |
schema |
"http://schema.org/#" |
OptionalString |
✅ |
PropertyValue |
"schema:PropertyValue" |
OptionalString |
✅ |
value |
"schema:value" |
OptionalString |
✅ |
discoverable / suspended / memorial / indexable |
strings | OptionalString |
✅ |
attributionDomains |
{"@id":..., "@type":...} |
JsonLdIdMappingSchema |
✅ |
showFeatured / showMedia / showRepliesInMedia |
strings | OptionalString |
✅ |
gts |
"https://gotosocial.org/ns#" |
OptionalString |
✅ |
interactionPolicy / canQuote / automaticApproval / manualApproval |
{"@id":..., "@type":...} |
JsonLdIdMappingSchema |
✅ |
✅ ActivityPubPersonSchema — все поля фикстуры покрыты
| Поле фикстуры | Тип в схеме | Статус |
|---|---|---|
id, preferredUsername, summary, inbox, outbox, followers, following |
required String |
✅ |
name |
OptionalString |
✅ |
webfinger, url, featured, featuredTags, published, featuredCollections |
OptionalString |
✅ |
manuallyApprovesFollowers, discoverable, indexable, memorial, showFeatured, showMedia, showRepliesInMedia |
OptionalBoolean |
✅ |
interactionPolicy |
MastodonInteractionPolicySchema |
✅ |
publicKey |
ActivityPubPublicKeySchema |
✅ |
tag |
Array(Union(Hashtag, Emoji)) |
✅ |
attachment |
Array(PropertyValue) |
✅ |
endpoints.sharedInbox |
ActivityPubEndpointsSchema |
✅ |
✅ followers?page=1 — живой ответ Mastodon
Реальный ответ от https://mastodon.social/users/nixCraft/followers?page=1:
{
"`@context`": "https://www.w3.org/ns/activitystreams",
"type": "OrderedCollectionPage",
"totalItems": 115147,
"next": "https://mastodon.social/users/nixCraft/followers?page=2",
"partOf": "https://mastodon.social/users/nixCraft/followers",
"orderedItems": ["https://mastodon.social/ap/users/...", ...]
}ActivityPubOrderedCollectionPageSchema покрывает его 1 в 1:
@context— строка →JsonLdContextSchema = Union(String, ...)✅type: "OrderedCollectionPage"✅totalItems,partOf,id,next(optional) ✅orderedItems: string[]— строки валидны какJsonValue✅
⚠️ Два мелких нюанса
-
tagиattachment— закрытые Struct-ы:ActivityPubActorAttachmentSchemaне расширяется открытымRecord. Если реальныйPropertyValueиз Mastodon содержит дополнительные поля (редко, но возможно), они будут молча удалены при декодировании. Если нужна полная forward-compat — добавить, Schema.Record({ key: Schema.String, value: JsonValueSchema })к каждому. -
interactionPolicyfilter:MastodonInteractionPolicySchemaтребует наличия хотя быcanFeatureилиcanQuote. Если актор вернёт пустой объект{}в полеinteractionPolicy— декодирование упадёт. Учитывая, что фикстура имеетcanFeature, это ок, но стоит знать об этом edge case.
Итог: код реализует Mastodon actor JSON и followers?page=1 на ~100% — все поля, включая все 22 ключа @context, покрыты корректными типами.
|
@coderabbitai please verify whether issue #334 and review 4334448958 are now fully implemented under exact 1:1 structural parity. I changed the contract from a permissive/superset model to exact schemas plus strict decoding: HTTP and parity tests use exactActivityPubParseOptions with onExcessProperty=error; ActivityPubPersonSchema is now exact local actor | exact Mastodon issue actor; Mastodon @context requires the exact 4-tuple and all 22 extension keys with @type: "@id"; Mastodon followers?page=1 requires the exact page keys and orderedItems: string[]; regression tests reject extra actor fields, missing/extra context terms, missing @type mappings, extra collection/page fields, arbitrary interactionPolicy, invalid actor extensions, and non-string followers items. Please confirm whether any proof gap remains, especially around exact 1:1 structure and no silently ignored new fields. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 33122
✅ Exact 1:1 Structural Parity — All Proof Obligations Satisfied
|
| Variant | Schema | Extra fields rejected |
|---|---|---|
| Local | LocalActivityPubPersonSchema — exact Struct, 13 required fields |
✅ via onExcessProperty: "error" |
| Mastodon issue | MastodonIssueActivityPubPersonSchema — exact Struct, 27 required fields |
✅ via onExcessProperty: "error" |
Mastodon @context exact 4-tuple, all 22 extensions required ✅
MastodonActorContextExtensionsSchema has all 22 keys non-optional (no Schema.optional anywhere):
| Key group | Enforced as |
|---|---|
manuallyApprovesFollowers, toot, schema, PropertyValue, value, discoverable, suspended, memorial, indexable, showFeatured, showMedia, showRepliesInMedia, gts |
Schema.String (required) |
featured, featuredTags, alsoKnownAs, movedTo, attributionDomains, interactionPolicy, canQuote, automaticApproval, manualApproval |
JsonLdIdMappingSchema = Struct({ "@id": String, "@type": Literal("@id") }) (required) |
Missing any one key → rejected. Extra key → rejected via onExcessProperty: "error".
Mastodon followers?page=1 exact keys + orderedItems: string[] ✅
MastodonFollowersOrderedCollectionPageSchema is an exact Struct:
{ "`@context`": Literal(activityStreams), id: String, type: Literal("OrderedCollectionPage"),
totalItems: Number, partOf: String, next: String, orderedItems: Array(String) }
- Matches the live Mastodon nixCraft fixture exactly (7 keys)
{ id: "not-a-link" }in orderedItems → rejected (not a string)prev: "..."extra field → rejected viaonExcessProperty: "error"
Regression tests cover all described rejection cases ✅
| Case | Test | Result |
|---|---|---|
Extra actor field extraField: "..." |
rejects non-exact ActivityPub fixture shapes |
isLeft ✅ |
Missing context term (manualApproval removed) |
same | isLeft ✅ |
Missing @type mapping on featured |
same | isLeft ✅ |
Extra context term (extraContextTerm) |
same | isLeft ✅ |
Mastodon collection + orderedItems: [] |
same | isLeft ✅ |
Mastodon page + prev: "..." |
same | isLeft ✅ |
orderedItems: ["ok", { id: "not-a-link" }] |
same | isLeft ✅ |
interactionPolicy: {} / { arbitrary: true } |
rejects structurally invalid...extensions |
isLeft ✅ |
Invalid icon/image/tag/attachment shapes |
same | isLeft ✅ |
page=2 returns 400 ApiBadRequestError |
rejects unsupported followers pages |
✅ |
Property-based tests ✅
12 fc.property tests across ActivityPubPersonSchema, ActivityPubOrderedCollectionSchema, ActivityPubOrderedCollectionPageSchema — accept correct shapes, reject wrong/missing literals and fields.
No proof gap remains. PR #335 implements exact 1:1 structural parity with the issue #334 Mastodon fixtures, enforced at both the schema layer and the HTTP boundary.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
packages/api/src/api/activitypub-schema.ts (2)
189-218:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftСхема Mastodon Person сейчас слишком узкая для заявленного parity-контракта.
На Line 201
nameсделан обязательным, и в схеме отсутствуют заявленные Mastodon-optional поля (alsoKnownAs,movedTo,suspended,attributionDomains,icon,image,devicesи т.д.). СexactActivityPubParseOptionsэто приведёт к ложным отклонениям валидных actor-документов.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/api/activitypub-schema.ts` around lines 189 - 218, MastodonIssueActivityPubPersonSchema is too strict: it marks name as required and omits many Mastodon-optional actor fields, which will cause valid actors to be rejected under exactActivityPubParseOptions; update MastodonIssueActivityPubPersonSchema to make name optional (use Schema.Optional) and add the optional fields declared by Mastodon parity such as alsoKnownAs (array/string), movedTo (string), suspended (boolean), attributionDomains (array/string), icon (ActivityPubImage/Object schema), image (ActivityPubImage/Object schema), devices (array/object), and any other optional fields already defined elsewhere (e.g., icon/image schemas or ActivityPubAttachment/Actor sub-schemas), ensuring each is wrapped with Schema.Optional and uses the appropriate existing schemas (e.g., ActivityPubActorAttachmentSchema, ActivityPubActorTagSchema, ActivityPubPublicKeySchema) so valid actor documents aren't falsely rejected.
155-162:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
interactionPolicyпропускает пустые approval-массивы.Текущий
Schema.filterпроверяет только наличие ключа, но не непустоту списков. Это ослабляет инвариант и пропускает структурно пустые policy-блоки.🔧 Предлагаемый фикс
const ActivityPubInteractionApprovalSchema = Schema.Struct({ automaticApproval: Schema.optional(Schema.Array(Schema.String)), manualApproval: Schema.optional(Schema.Array(Schema.String)) }).pipe( - Schema.filter((approval) => - approval.automaticApproval !== undefined || - approval.manualApproval !== undefined) + Schema.filter((approval) => + (approval.automaticApproval?.length ?? 0) > 0 || + (approval.manualApproval?.length ?? 0) > 0) )Also applies to: 164-171
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/api/activitypub-schema.ts` around lines 155 - 162, The current ActivityPubInteractionApprovalSchema uses Schema.filter to only check for the presence of automaticApproval/manualApproval keys but allows empty arrays; update the filter predicate so it only passes when at least one of automaticApproval or manualApproval is a non-empty array (check length > 0), i.e., ensure approval arrays are defined and have items. Apply the same change to the other analogous schema in the file that uses the same pattern (the schema covering lines ~164-171) so both enforce non-empty approval arrays.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@packages/api/src/api/activitypub-schema.ts`:
- Around line 189-218: MastodonIssueActivityPubPersonSchema is too strict: it
marks name as required and omits many Mastodon-optional actor fields, which will
cause valid actors to be rejected under exactActivityPubParseOptions; update
MastodonIssueActivityPubPersonSchema to make name optional (use Schema.Optional)
and add the optional fields declared by Mastodon parity such as alsoKnownAs
(array/string), movedTo (string), suspended (boolean), attributionDomains
(array/string), icon (ActivityPubImage/Object schema), image
(ActivityPubImage/Object schema), devices (array/object), and any other optional
fields already defined elsewhere (e.g., icon/image schemas or
ActivityPubAttachment/Actor sub-schemas), ensuring each is wrapped with
Schema.Optional and uses the appropriate existing schemas (e.g.,
ActivityPubActorAttachmentSchema, ActivityPubActorTagSchema,
ActivityPubPublicKeySchema) so valid actor documents aren't falsely rejected.
- Around line 155-162: The current ActivityPubInteractionApprovalSchema uses
Schema.filter to only check for the presence of automaticApproval/manualApproval
keys but allows empty arrays; update the filter predicate so it only passes when
at least one of automaticApproval or manualApproval is a non-empty array (check
length > 0), i.e., ensure approval arrays are defined and have items. Apply the
same change to the other analogous schema in the file that uses the same pattern
(the schema covering lines ~164-171) so both enforce non-empty approval arrays.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5ff86183-c9b9-495f-9968-7e0fc39eda3c
📒 Files selected for processing (7)
packages/api/src/api/activitypub-schema.tspackages/api/src/api/contracts.tspackages/api/src/api/schema.tspackages/api/src/http.tspackages/api/src/services/federation.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/tests/http-config.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: E2E (Clone auto-open SSH)
- GitHub Check: Test
- GitHub Check: E2E (Runtime volumes + SSH)
- GitHub Check: E2E (Clone cache)
- GitHub Check: E2E (Browser command)
- GitHub Check: E2E (Login context)
- GitHub Check: Lint
- GitHub Check: E2E (OpenCode)
- GitHub Check: Final build (windows-latest)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Implement Functional Core, Imperative Shell (FCIS) pattern: CORE layer contains only pure functions with immutable data and mathematical operations; SHELL layer isolates all effects (IO, network, database). Strict dependency direction: SHELL → CORE (never reverse).
Never useany,unknown,eslint-disable,ts-ignore, orastype assertions (except in rigorously justified cases with documentation). Always use exhaustive union type analysis through.exhaustive()pattern matching.
All external dependencies must be wrapped through typed interfaces and injected via Effect-TS Layer pattern. Never call external services directly from CORE functions.
Use monadic composition with Effect-TS for all effects:Effect<Success, Error, Requirements>. Compose effects throughpipe()andEffect.flatMap(). Implement dependency injection via Layer pattern. Handle errors without try/catch blocks.
All functions must be pure in the CORE layer: no side effects (logging, console output, IO operations, mutations). Separate all side effects into the SHELL layer.
Use exhaustive pattern matching with Effect.Match instead of switch statements. Example:Match.value(item).pipe(Match.when(...), Match.exhaustive).
Document all functions with comprehensive TSDoc including:@pure(true/false),@effect(required services),@invariant(mathematical invariants),@precondition,@postcondition,@complexity(time and space),@throwsNever (errors must be typed in Effect).
Use functional comment markers for code clarity: CHANGE (brief description), WHY (mathematical/architectural justification), QUOTE(ТЗ) (requirement citation), REF (RTM or message ID), SOURCE (external source with quote), FORMAT THEOREM (∀x ∈ Domain: P(x) → Q(f(x))), PURITY (CORE|SHELL), EFFECT (Effect type signature), INVARIANT (mathematical invariant), COMPLEXITY (time/space).
Define all external service dependencies as Context.Tag classes with fully typed methods returning Effect types. Example: `class Da...
Files:
packages/api/src/api/schema.tspackages/api/tests/http-config.test.tspackages/api/src/api/contracts.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/http.tspackages/api/src/services/federation.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Forbidden constructs in CORE code:any,eslint-disable,ts-ignore,async/await, raw Promise chains (then/catch),Promise.all,try/catchfor logic control,console.*, switch statements (use Match with .exhaustive() instead)
All functions must use Effect-TS for composing effects:Effect<Success, Error, Requirements>. No direct async/await, Promise chains, or try/catch in product logic.
Functional comments must include: CHANGE, WHY, QUOTE(ТЗ) or n/a, REF, SOURCE or n/a, FORMAT THEOREM, PURITY (CORE|SHELL), EFFECT signature for SHELL functions, INVARIANT, and COMPLEXITY.
All data mutations must use immutable patterns (ReadonlyArray, readonly properties, Object.freeze); mutation in SHELL only when absolutely necessary and documented.
Files:
packages/api/src/api/schema.tspackages/api/tests/http-config.test.tspackages/api/src/api/contracts.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/http.tspackages/api/src/services/federation.ts
**/*.{sh,bash,py,js,ts,jsx,tsx,go,java,rb,php}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files introduce command injection or unsafe shell/process execution with user-controlled input
Files:
packages/api/src/api/schema.tspackages/api/tests/http-config.test.tspackages/api/src/api/contracts.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/http.tspackages/api/src/services/federation.ts
**/*.{py,js,ts,jsx,tsx,go,java,rb,php,sh,bash,c,cpp}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files introduce path traversal or writes outside intended project/container state directories
Files:
packages/api/src/api/schema.tspackages/api/tests/http-config.test.tspackages/api/src/api/contracts.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/http.tspackages/api/src/services/federation.ts
**/*.{js,ts,jsx,tsx,py,java,go,rb,php,sh,bash,yml,yaml,json,env*,toml,cfg,config,dockerfile,dockerignore}
📄 CodeRabbit inference engine (Custom checks)
Fail if changed files expose credentials, tokens, private-keys, or PII in source, generated config, logs, or CI output
Files:
packages/api/src/api/schema.tspackages/api/tests/http-config.test.tspackages/api/src/api/contracts.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/http.tspackages/api/src/services/federation.ts
packages/api/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (README.md)
API layer should work only with LIB and serve as the intermediary between APP and LIB
Files:
packages/api/src/api/schema.tspackages/api/tests/http-config.test.tspackages/api/src/api/contracts.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/http.tspackages/api/src/services/federation.ts
**/*
⚙️ CodeRabbit configuration file
**/*: Ты строгий ревьюер SPEC DRIVEN DEVELOPMENT.Перед выводами изучи README.md, другие *.md файлы, linked issues,
PR description, PR comments/discussion и релевантную кодовую базу.Сверь изменения с исходным ТЗ/спекой и обсуждением. Флагай любой уход
от спеки, недокументированное изменение поведения, отсутствие тестов
для заявленного поведения и security-риск. Если спека не видна,
попроси автора добавить ее в issue или PR description.Проверь решение с точки зрения формальной верификации: какие инварианты,
предусловия и постусловия можно доказать математически, а где доказуемость
слабая. Оцени решение с точки зрения теории игр: устойчивы ли стимулы,
нет ли выгодного обхода правил, и какое решение было бы сильнее.
Files:
packages/api/src/api/schema.tspackages/api/tests/http-config.test.tspackages/api/src/api/contracts.tspackages/api/tests/activitypub-schema-parity.test.tspackages/api/src/api/activitypub-schema.tspackages/api/src/http.tspackages/api/src/services/federation.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.{ts,tsx}: Implement property-based testing using fast-check for mathematical properties and invariants. Example:fc.property(fc.array(messageArbitrary), (messages) => isChronologicallySorted(sortMessagesByTimestamp(messages))).
Mock external dependencies in unit tests using Effect's testing utilities. Run tests without Effect runtime for speed. Example:Effect.provide(MockService), Effect.runPromise.
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Property-based tests (fast-check) must verify mathematical invariants; unit tests must use Effect test utilities without async/await.
Files:
packages/api/tests/http-config.test.tspackages/api/tests/activitypub-schema-parity.test.ts
🧠 Learnings (1)
📚 Learning: 2026-05-18T07:43:38.131Z
Learnt from: skulidropek
Repo: ProverCoderAI/docker-git PR: 313
File: packages/api/src/services/panel-cloudflare-tunnel.ts:123-135
Timestamp: 2026-05-18T07:43:38.131Z
Learning: In this repo’s ProverCoderAI/docker-git architecture, it is intentional for service modules under packages/api/src/services/ to call Node.js APIs directly (e.g., fs, child_process, fetch). Do not treat direct Node API usage in these service modules as a dependency-injection (DI) violation. This codebase provides the NodeContext at the program boundary (entry point) rather than injecting Context.Tag/Layer services into individual service modules; reviewers should only flag DI/context issues if the expected boundary setup is missing.
Applied to files:
packages/api/src/services/federation.ts
🔇 Additional comments (6)
packages/api/src/api/contracts.ts (1)
12-16: LGTM!Also applies to: 629-640
packages/api/src/api/schema.ts (1)
14-23: LGTM!packages/api/src/http.ts (1)
47-49: LGTM!Also applies to: 320-330, 343-351, 719-739
packages/api/src/services/federation.ts (1)
33-36: LGTM!Also applies to: 630-709, 725-767, 1595-1605
packages/api/tests/activitypub-schema-parity.test.ts (1)
12-20: LGTM!Also applies to: 243-445, 446-558
packages/api/tests/http-config.test.ts (1)
15-17: LGTM!Also applies to: 141-155, 274-300
|
@coderabbitai можешь проверить на сколько код реализует {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://purl.archive.org/socialweb/webfinger",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","toot":"http://joinmastodon.org/ns#","featured":{"@id":"toot:featured","@type":"@id"},"featuredTags":{"@id":"toot:featuredTags","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"movedTo":{"@id":"as:movedTo","@type":"@id"},"schema":"http://schema.org/#","PropertyValue":"schema:PropertyValue","value":"schema:value","discoverable":"toot:discoverable","suspended":"toot:suspended","memorial":"toot:memorial","indexable":"toot:indexable","attributionDomains":{"@id":"toot:attributionDomains","@type":"@id"},"showFeatured":"toot:showFeatured","showMedia":"toot:showMedia","showRepliesInMedia":"toot:showRepliesInMedia","gts":"https://gotosocial.org/ns#","interactionPolicy":{"@id":"gts:interactionPolicy","@type":"@id"},"canQuote":{"@id":"gts:canQuote","@type":"@id"},"automaticApproval":{"@id":"gts:automaticApproval","@type":"@id"},"manualApproval":{"@id":"gts:manualApproval","@type":"@id"}}],"id":"https://mastodon.social/users/GordonFreeman","webfinger":"GordonFreeman@mastodon.social","type":"Person","following":"https://mastodon.social/users/GordonFreeman/following","followers":"https://mastodon.social/users/GordonFreeman/followers","inbox":"https://mastodon.social/users/GordonFreeman/inbox","outbox":"https://mastodon.social/users/GordonFreeman/outbox","featured":"https://mastodon.social/users/GordonFreeman/collections/featured","featuredTags":"https://mastodon.social/users/GordonFreeman/collections/tags","preferredUsername":"GordonFreeman","name":"GordonFreeman","summary":"","url":"https://mastodon.social/@GordonFreeman","manuallyApprovesFollowers":false,"discoverable":false,"indexable":false,"published":"2022-05-11T00:00:00Z","memorial":false,"showFeatured":true,"showMedia":true,"showRepliesInMedia":true,"interactionPolicy":{"canFeature":{"automaticApproval":["https://mastodon.social/users/GordonFreeman"]}},"featuredCollections":"https://mastodon.social/ap/users/108283196203417442/featured_collections","publicKey":{"id":"https://mastodon.social/users/GordonFreeman#main-key","owner":"https://mastodon.social/users/GordonFreeman","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zWlvp7jHBsNV/3QkBnu\nZpbtPWetfBFJsK7huV3ZLsUk+oeyAPQKO4KCiZiBZm3qVBCr6O7p75GAesXRpMVN\ngkrgzFCI4ZME0x/6VrVNvo4q8xpRxu+rAsULXa1mI9XiUDDcFTIJn8wW09uGa4XK\nW0KGwe2+F6q7OVJfkiAXbwRrjBg6Stn8NOd6n/TYFw4UnyMNZ6BPqnxxhEv1GsFn\n6viF/jrT6SpIY0K6kBlMk5AepFj/5LpoiTdH7bfpja3jbGQNF/h1PJ0U1kP4JvNT\nNOnWMS+5fMovjftxPwwqggB8FrHeDF1QOk8ETznugukqhtNpLTx28P+sEAJC8eZS\nPwIDAQAB\n-----END PUBLIC KEY-----\n"},"tag":[],"attachment":[],"endpoints":{"sharedInbox":"https://mastodon.social/inbox"}} Skuli Dropek, [19.05.2026 22:04] Skuli Dropek, [19.05.2026 22:06] Skuli Dropek, [19.05.2026 21:43] |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 8763
✅
|
| Ключ фикстуры | Значение | Схема | Статус |
|---|---|---|---|
manuallyApprovesFollowers |
"as:..." |
Schema.String |
✅ |
toot |
"http://joinmastodon.org/ns#" |
Schema.String |
✅ |
featured |
{"@id":"toot:featured","@type":"@id"} |
JsonLdIdMappingSchema |
✅ |
featuredTags |
{"@id":..., "@type":"@id"} |
JsonLdIdMappingSchema |
✅ |
alsoKnownAs |
{"@id":..., "@type":"@id"} |
JsonLdIdMappingSchema |
✅ |
movedTo |
{"@id":..., "@type":"@id"} |
JsonLdIdMappingSchema |
✅ |
schema |
"http://schema.org/#" |
Schema.String |
✅ |
PropertyValue |
"schema:PropertyValue" |
Schema.String |
✅ |
value |
"schema:value" |
Schema.String |
✅ |
discoverable/suspended/memorial/indexable |
strings | Schema.String |
✅ |
attributionDomains |
{"@id":..., "@type":"@id"} |
JsonLdIdMappingSchema |
✅ |
showFeatured/showMedia/showRepliesInMedia |
strings | Schema.String |
✅ |
gts |
"https://gotosocial.org/ns#" |
Schema.String |
✅ |
interactionPolicy/canQuote/automaticApproval/manualApproval |
{"@id":..., "@type":"@id"} |
JsonLdIdMappingSchema |
✅ |
Все 22 ключа — required (без Schema.optional). Лишний ключ → отклонение через onExcessProperty: "error".
✅ MastodonIssueActivityPubPersonSchema — все 27 полей фикстуры
| Поле фикстуры | Тип | Схема | Статус |
|---|---|---|---|
id, webfinger, preferredUsername, name, summary, url, published, featuredCollections |
string | Schema.String |
✅ |
following, followers, inbox, outbox, featured, featuredTags |
string | Schema.String |
✅ |
manuallyApprovesFollowers, discoverable, indexable, memorial, showFeatured, showMedia, showRepliesInMedia |
boolean | Schema.Boolean |
✅ |
interactionPolicy |
{canFeature:{automaticApproval:[...]}} |
MastodonInteractionPolicySchema |
✅ |
publicKey |
{id, owner, publicKeyPem} |
ActivityPubPublicKeySchema |
✅ |
tag |
[] |
Array(Union(Hashtag,Emoji)) |
✅ |
attachment |
[] |
Array(PropertyValue) |
✅ |
endpoints |
{sharedInbox:...} |
ActivityPubEndpointsSchema |
✅ |
✅ interactionPolicy — фиктура декодируется корректно
Фикстура: {"canFeature":{"automaticApproval":["https://mastodon.social/users/GordonFreeman"]}}
MastodonInteractionPolicySchema— filter:canFeatureприсутствует → проходит ✅ActivityPubInteractionApprovalSchema— filter:automaticApprovalприсутствует → проходит ✅
✅ followers?page=1 (nixCraft live shape)
MastodonFollowersOrderedCollectionPageSchema:
| Поле | Live Mastodon | Схема | Статус |
|---|---|---|---|
@context |
"https://www.w3.org/ns/activitystreams" |
Literal(activityStreams) |
✅ |
id |
URL страницы | Schema.String |
✅ |
type |
"OrderedCollectionPage" |
Literal(...) |
✅ |
totalItems |
число | Schema.Number |
✅ |
partOf |
URL коллекции | Schema.String |
✅ |
next |
URL следующей страницы | Schema.String |
✅ |
orderedItems |
["https://..."] |
Array(Schema.String) |
✅ |
Итог
Код реализует оба JSON-шейпа (Mastodon actor + followers?page=1) с точностью 1 в 1:
- Все 22 ключа
@contextс правильными типами (String vs JsonLdIdMappingSchema) - Все 27 полей Person-актора
- Точная структура
interactionPolicy onExcessProperty: "error"на HTTP-границе и в тестах — ни одно лишнее поле не проходит молча- Пустые массивы
tag:[]иattachment:[]валидны какArray(...)схемы
Summary
/federation/followers?page=1as an exactOrderedCollectionPage.interactionPolicy, exact excess-property rejection, property tests, and unsupported followers page rejection.Closes #334.
Proof: Exact 1:1 Type Correspondence
Sources
Personactor example and requestedfollowers?page=1shape.https://forgefed.org/spec/.https://www.w3.org/TR/activitypub/.https://www.w3.org/TR/activitystreams-core/.https://mastodon.social/users/GordonFreemanhttps://mastodon.social/users/nixCraft/followershttps://mastodon.social/users/nixCraft/followers?page=1Invariants
ActivityPubPerson,ActivityPubOrderedCollection,ActivityPubOrderedCollectionPage,ActivityPubFollowActivity,ForgeFedTicket, andActivityPubPublicKeyareSchema.Schema.Type<typeof ...Schema>. Therefore the public TypeScript type and runtime decoder are derived from the same schema contract.exactActivityPubParseOptions = { onExcessProperty: "error" }. The HTTP response boundary and parity tests callSchema.decodeUnknown(..., exactActivityPubParseOptions), so extra fields fail instead of being silently dropped.Schema.Unknown; collectionorderedItems, ticketraw, and ticketattachmentare bounded byJsonValue.ActivityPubPersonSchemais a union of exact local actor shape and exact Mastodon issue actor shape. The local actor requires exactly the local protocol fields emitted by docker-git. The Mastodon actor requires exactly the issue fixture fields and rejects top-level extras such asicon,image,devices,alsoKnownAs, or any other field not present in the issue JSON.[ActivityStreams, Security, ForgeFed]. Mastodon actors decode only with[ActivityStreams, Security, SocialWeb WebFinger, extension-object]; the extension object requires all 22 issue keys, requires mapping objects to contain@idand literal@type: "@id", and rejects missing or extra context terms.attachmentrequires{ type: "PropertyValue", name, value };tagis a discriminated union of{ type: "Hashtag", name, href }and{ type: "Emoji", id, name, icon }; nested image objects require{ type: "Image", url }. Empty actor extension objects and wrong literal variants decode toLeft.interactionPolicyaccepts only MastodoncanFeature/canQuoteblocks, and each block must containautomaticApprovalormanualApproval. Empty policy objects, arbitrary policy keys, and arbitrary nested approval objects decode toLeft.ActivityPubOrderedCollectionSchemais a union of exact local collection, exact local followers collection, and exact Mastodon followers collection.ActivityPubOrderedCollectionPageSchemais a union of exact local followers page and exact Mastodon followers page.followers?page=1decodes only as@context: ActivityStreams,type: "OrderedCollectionPage",id,totalItems,partOf,next, andorderedItems: string[]. Embedded objects,prev, and other unlisted keys decode toLeft.din{ actor, outbox, followers, followers?page=1, following, liked }, the HTTP handler returns200only after strictSchema.decodeUnknown(S_d, exactActivityPubParseOptions)(d)succeeds.Person,OrderedCollection, andOrderedCollectionPage.keys(decoded) = keys(fixture), so the schema is not proving success by silently dropping fields.@typemappings, extra context terms, extra collection/page fields, and non-string Mastodon followers page items./federation/followersreturnsOrderedCollection;/federation/followers?page=1returnsOrderedCollectionPagewithpartOf = /federation/followers;/federation/followers?page=2fails with typedApiBadRequestErrorand HTTP 400.Theorem
Let
DecodeExact(S, x) = Schema.decodeUnknown(S, exactActivityPubParseOptions)(x).For each supported federation document
d, if the API returnsdsuccessfully, then there exists a schemaS_dsuch that:For the issue/Mastodon actor fixture
f_actor:For the issue/Mastodon followers page fixture
f_page:For invalid payloads
x:So the proof obligation is executable: it is enforced by the HTTP response path, schema-derived TypeScript contracts, exact fixture parity tests, property tests, actor-extension counterexample tests, collection/page exactness tests, and negative HTTP tests.
Verification
Latest local verification on commit
a5bcc9b:bun run --cwd packages/api typecheck bun run --cwd packages/api lint bun run --cwd packages/api testFull API test result: 24 test files, 177 tests passed.