package server import ( "context" "errors" "net/http" "strings" "chat-agent-system/internal/auth" "chat-agent-system/internal/authz" "chat-agent-system/internal/docrevision" "chat-agent-system/internal/freedoc" "chat-agent-system/internal/projectinstance" ) type docRevisionRef struct { Kind string // controlled|freeform|orphan ProjectID int64 WorkDir string LifecycleState string Markdown string Title string AuthorID int64 } func (s *Server) resolveDocRevisionRef(ctx context.Context, user *auth.UserAccount, docID string) (*docRevisionRef, error) { if strings.TrimSpace(docID) == "" { return nil, docrevision.ErrInvalidDocID } if s.gov() != nil { if doc, err := s.gov().GetDocument(ctx, docID); err == nil && doc != nil { ref := &docRevisionRef{ Kind: "controlled", ProjectID: doc.ProjectID, WorkDir: doc.WorkDir, LifecycleState: string(doc.CurrentState), AuthorID: doc.AuthorID, } if s.docRev() != nil { if md, _, herr := s.docRev().HeadContent(ctx, docID); herr == nil && strings.TrimSpace(md) != "" { ref.Markdown = md return ref, nil } } if content, cErr := s.gov().ReadDocumentContent(ctx, docID, s.dataRoot()); cErr == nil && content != nil { ref.Markdown = content.Markdown } return ref, nil } } if s.freeDoc() != nil && user != nil { if detail, err := s.freeDoc().GetDocument(ctx, int(user.ID), docID); err == nil && detail != nil { return &docRevisionRef{ Kind: "freeform", ProjectID: int64(detail.Document.ProjectID), LifecycleState: "drafting", Markdown: detail.Content, Title: detail.Document.Title, }, nil } else if errors.Is(err, freedoc.ErrForbidden) { return nil, docrevision.ErrForbidden } } return &docRevisionRef{Kind: "orphan", LifecycleState: "drafting"}, nil } func (s *Server) authorizeDocRevision(ctx context.Context, user *auth.UserAccount, ref *docRevisionRef, write bool) error { if user == nil || ref == nil { return authz.ErrForbidden } if ref.Kind == "orphan" { // Unknown doc IDs: never bootstrap a silent fake ledger (read or write). return docrevision.ErrNotFound } if ref.ProjectID > 0 { if s.authorizer() == nil { if s.failClosedAuthz() { return authz.ErrForbidden } } else if err := s.authorizer().RequireProjectAccessOrAdmin(ctx, ref.ProjectID, user.ID, s.isSystemAdminUser(ctx, user)); err != nil { return err } } else if ref.Kind == "controlled" { return authz.ErrForbidden } if write && ref.LifecycleState != "" && ref.LifecycleState != "drafting" { return docrevision.ErrLifecycleLocked } if write && ref.Kind == "controlled" && ref.AuthorID > 0 && user.ID != ref.AuthorID && !s.isSystemAdminUser(ctx, user) { return docrevision.ErrForbidden } return nil } func (s *Server) requireDocRevisionAccess(w http.ResponseWriter, r *http.Request, write bool) (*auth.UserAccount, string, *docRevisionRef, bool) { user, ok := s.requirePrincipal(w, r) if !ok { return nil, "", nil, false } docID := strings.TrimSpace(r.PathValue("doc_id")) if docID == "" { writeDocRevisionError(w, docrevision.ErrInvalidDocID) return nil, "", nil, false } ref, err := s.resolveDocRevisionRef(r.Context(), user, docID) if err != nil { writeDocRevisionError(w, err) return nil, "", nil, false } if err := s.authorizeDocRevision(r.Context(), user, ref, write); err != nil { if errors.Is(err, authz.ErrForbidden) || errors.Is(err, authz.ErrUnauthenticated) { writeAuthzError(w, err) return nil, "", nil, false } writeDocRevisionError(w, err) return nil, "", nil, false } if write && !s.enforceDocGovWorkflowAction(w, r.Context(), user.ID, docID, projectinstance.DocAccessActionEdit) { return nil, "", nil, false } return user, docID, ref, true } func (s *Server) dataRoot() string { if s != nil && s.cfg != nil && s.cfg.DataDir != "" { return s.cfg.DataDir } return "." } func (s *Server) bootstrapRevisionIfNeeded(ctx context.Context, user *auth.UserAccount, docID string, ref *docRevisionRef) (*docrevision.RevisionState, error) { rev := s.docRev() if rev == nil { return nil, errors.New("docrevision unavailable") } st, err := rev.GetRevisionState(ctx, docID) if err == nil { return st, nil } if !errors.Is(err, docrevision.ErrNotFound) { return nil, err } if ref != nil && ref.Kind == "orphan" { return nil, docrevision.ErrNotFound } md := "" if ref != nil { md = ref.Markdown } if strings.TrimSpace(md) == "" { return nil, docrevision.ErrNeedsContent } kind := "controlled" life := "drafting" var projectID int64 workDir := "" if ref != nil { if ref.Kind != "" { kind = ref.Kind } life = ref.LifecycleState projectID = ref.ProjectID workDir = ref.WorkDir } actor := docrevision.ActorSnapshot{DisplayName: "bootstrap", Role: "system"} if user != nil { actor = docrevision.ActorSnapshot{DisplayName: user.Name, Role: "author", Duty: "author"} } return rev.EnsureBootstrap(ctx, docrevision.BootstrapRequest{ DocID: docID, Kind: kind, Markdown: md, ProjectID: projectID, WorkDir: workDir, LifecycleState: life, Actor: actor, }) }