{"record":{"id":"9f754ec2408cdc51","repo":"toeverything/AFFiNE","slug":"expect-to-publish-doc","errorCode":"expect_to_publish_doc","errorMessage":"Expected to publish a doc, not a Space.","messagePattern":"Expected to publish a doc, not a Space\\.","errorType":"exception","errorClass":"ExpectToPublishDoc","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/doc.ts","lineNumber":431,"sourceCode":"  @Mutation(() => DocType)\n  async publishDoc(\n    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args('docId') docId: string,\n    @Args({\n      name: 'mode',\n      type: () => PublicDocMode,\n      nullable: true,\n      defaultValue: PublicDocMode.Page,\n    })\n    mode: PublicDocMode\n  ) {\n    if (workspaceId === docId) {\n      this.logger.error('Expect to publish doc, but it is a workspace', {\n        workspaceId,\n        docId,\n      });\n      throw new ExpectToPublishDoc();\n    }\n\n    await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish');\n    await this.inviteQuota.assertWorkspaceActionAllowed({\n      actorUserId: user.id,\n      workspaceId,\n      docId,\n      action: 'publishDoc',\n    });\n\n    const doc = await this.models.doc.publish(workspaceId, docId, mode);\n    this.event.emit('doc.public_state.changed', { workspaceId, docId });\n\n    this.logger.log(\n      `Publish page ${docId} with mode ${mode} in workspace ${workspaceId}`\n    );\n\n    return doc;","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b6de0ad51b76f3daac2d3d6325369ea623ed7ed4/packages/backend/server/src/core/workspaces/resolvers/doc.ts#L413-L449","documentation":"publishDoc rejects any call where docId === workspaceId. In AFFiNE the workspace root id equals the workspace id and the root is a Space container, not a publishable page, so publishing 'the workspace id itself' is always a client mistake and is refused with ExpectToPublishDoc before any permission check.","triggerScenarios":"Calling publishDoc(workspaceId, docId) with docId set to the workspace id — typically a client passing the root page id, defaulting docId to the workspace id, or reading the id from the wrong field of a workspace object.","commonSituations":"Porting older client code where the root page was addressed by the workspace id; constructing the mutation arguments from a workspace object instead of a page object.","solutions":["Pass the actual page id: assert docId !== workspaceId in the client before calling publishDoc","If the target really is the workspace root, pick a child page — the root itself cannot be published","Check where the client sources docId from; it must come from a page/doc object, not the workspace"],"exampleFix":"// before\nawait publishDoc(ws.id, ws.id, mode); // root is a Space -> EXPECT_TO_PUBLISH_DOC\n\n// after\nconst page = pickPageToPublish(ws); // a real page id, never the workspace id\nif (page.id === ws.id) throw new Error('cannot publish the workspace root');\nawait publishDoc(ws.id, page.id, mode);","handlingStrategy":"validation","validationCode":"// Never publish the workspace root\nfunction assertPublishablePageId(workspaceId: string, docId: string) {\n  if (!docId || docId === workspaceId) {\n    throw new Error('docId must be a page id, not the workspace id');\n  }\n}\nassertPublishablePageId(ws.id, docId);\nawait publishDoc(ws.id, docId, mode);","typeGuard":"function isExpectToPublishDoc(e: unknown): boolean {\n  return (\n    typeof e === 'object' && e !== null &&\n    (e as { extensions?: { code?: string } }).extensions?.code === 'expect_to_publish_doc'\n  );\n}","tryCatchPattern":"try {\n  await publishDoc(ws.id, docId, mode);\n} catch (e) {\n  if (isExpectToPublishDoc(e)) {\n    promptUserToSelectAnActualPage(); // root can never be published\n  } else throw e;\n}","preventionTips":["Source docId only from page/doc objects, never from workspace objects","Disable the publish action on the workspace root node in the UI","Add an id inequality assert in shared client helpers around all doc mutations"],"tags":["doc","publish","validation","affine"],"backgroundTag":"invalid-identifier","analyzedSha":"b6de0ad51b76f3daac2d3d6325369ea623ed7ed4","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}