{"record":{"id":"5c813974b051f0f4","repo":"toeverything/AFFiNE","slug":"expect-to-revoke-public-doc","errorCode":"expect_to_revoke_public_doc","errorMessage":"Expect doc not to be workspace","messagePattern":"Expect doc not to be workspace","errorType":"exception","errorClass":"ExpectToRevokePublicDoc","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/doc.ts","lineNumber":505,"sourceCode":"    this.logger.log(\n      `Publish page ${docId} with mode ${mode} in workspace ${workspaceId}`\n    );\n\n    return doc;\n  }\n\n  @Mutation(() => DocType)\n  async revokePublicDoc(\n    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args('docId') docId: string\n  ) {\n    if (workspaceId === docId) {\n      this.logger.error('Expect to revoke public doc, but it is a workspace', {\n        workspaceId,\n        docId,\n      });\n      throw new ExpectToRevokePublicDoc('Expect doc not to be workspace');\n    }\n\n    await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish');\n\n    const doc = await this.models.doc.unpublish(workspaceId, docId);\n    this.event.emit('doc.public_state.changed', { workspaceId, docId });\n\n    this.logger.log(`Revoke public doc ${docId} in workspace ${workspaceId}`);\n\n    return doc;\n  }\n\n  private async tryFixDocOwner(workspaceId: string, docId: string) {\n    const allowed = await this.cache.setnx(\n      `fixingOwner:${workspaceId}:${docId}`,\n      1,\n      // TODO(@forehalo): we definitely need a timer helper\n      { ttl: 1000 * 60 * 60 * 24 }","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/resolvers/doc.ts#L487-L523","documentation":"Thrown by revokePublicDoc when workspaceId === docId. Same rationale as publishDoc: the workspace root cannot be 'unpublished', so the server refuses with ExpectToRevokePublicDoc (invalid_input) and logs at error level.","triggerScenarios":"Calling revokePublicDoc with docId equal to workspaceId - attempting to revoke publication of the workspace root doc.","commonSituations":"UI bug offering a 'revoke public' action on the workspace root; reused id variable; client not filtering the workspace id out of revocable docs.","solutions":["Pass the actual public child page id as docId, not the workspace id.","Disable the revoke action client-side when docId === workspaceId.","Fix the data source that presented the workspace root as a public doc."],"exampleFix":"// before\nawait gql.revokePublicDoc({ workspaceId, docId: workspaceId });\n\n// after\nif (docId === workspaceId) return; // nothing to revoke\nawait gql.revokePublicDoc({ workspaceId, docId });","handlingStrategy":"validation","validationCode":"// Never revoke on the workspace root\nif (docId === workspaceId) {\n  throw new Error('Cannot revoke public state of the workspace root');\n}\nawait gql.revokePublicDoc({ workspaceId, docId });","typeGuard":"function isExpectToRevokePublicDoc(e: unknown): boolean {\n  return (\n    typeof e === 'object' &&\n    e !== null &&\n    (e as any).extensions?.code === 'expect_to_revoke_public_doc'\n  );\n}","tryCatchPattern":"try {\n  await gql.revokePublicDoc({ workspaceId, docId });\n} catch (e) {\n  if (isExpectToRevokePublicDoc(e)) {\n    reportBug('revokePublicDoc called with workspaceId as docId');\n    return;\n  }\n  throw e;\n}","preventionTips":["Filter the workspace root out of the 'public docs' list shown for revoke.","Assert docId !== workspaceId before invoking revokePublicDoc.","Use distinct variables for the selected doc versus the owning workspace."],"tags":["graphql","doc","publishing","validation"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}