WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Stable resource reference and lifecycle constants.
|
||||
const (
|
||||
ResourceScheme = "resource://"
|
||||
ResourceHandleLength = 22
|
||||
ResourceStateActive = "active"
|
||||
ResourceStateDeleted = "deleted"
|
||||
ResourceLifecyclePersistent = "persistent"
|
||||
ResourceLifecycleTemporary = "temporary"
|
||||
)
|
||||
|
||||
// Resource binding owner types. A binding is a claim on a stored object: the
|
||||
// bytes live as long as at least one owner still claims them, which is what
|
||||
// lets a chat answer be saved into the knowledge base without copying its
|
||||
// files, and lets either copy be deleted without breaking the other.
|
||||
const (
|
||||
ResourceOwnerKnowledge = "knowledge"
|
||||
ResourceOwnerMessage = "message"
|
||||
ResourceOwnerTemporaryDocument = "temporary_document"
|
||||
)
|
||||
|
||||
// Resource binding relations describe why an owner claims a resource.
|
||||
const (
|
||||
ResourceRelationSourceFile = "source_file"
|
||||
ResourceRelationExtractedImage = "extracted_image"
|
||||
ResourceRelationArtifact = "artifact"
|
||||
ResourceRelationAttachment = "attachment"
|
||||
)
|
||||
|
||||
// StoredResource is the stable application identity of one stored object. PhysicalPath
|
||||
// is deliberately internal: API responses, persisted rich text and LLM prompts
|
||||
// use resource://<handle> instead.
|
||||
type StoredResource struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||
Handle string `json:"handle" gorm:"type:varchar(22);not null;uniqueIndex"`
|
||||
TenantID uint64 `json:"tenant_id" gorm:"not null;index"`
|
||||
StorageBackendID string `json:"storage_backend_id,omitempty" gorm:"type:varchar(36);index"`
|
||||
Provider string `json:"provider" gorm:"type:varchar(32);not null"`
|
||||
PhysicalPath string `json:"-" gorm:"type:text;not null"`
|
||||
LocationHash string `json:"-" gorm:"type:varchar(64);not null"`
|
||||
Kind string `json:"kind" gorm:"type:varchar(32);not null;default:'file'"`
|
||||
MimeType string `json:"mime_type,omitempty" gorm:"type:varchar(255);not null;default:''"`
|
||||
OriginalName string `json:"original_name,omitempty" gorm:"type:varchar(1024);not null;default:''"`
|
||||
Size int64 `json:"size" gorm:"not null;default:0"`
|
||||
ContentHash string `json:"content_hash,omitempty" gorm:"type:varchar(64);not null;default:''"`
|
||||
Lifecycle string `json:"lifecycle" gorm:"type:varchar(16);not null;default:'persistent'"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
State string `json:"state" gorm:"type:varchar(16);not null;default:'active'"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// TableName returns the resource registry table name.
|
||||
func (StoredResource) TableName() string { return "resources" }
|
||||
|
||||
// BeforeCreate fills resource defaults before persistence.
|
||||
func (r *StoredResource) BeforeCreate(_ *gorm.DB) error {
|
||||
if r.ID == "" {
|
||||
r.ID = uuid.NewString()
|
||||
}
|
||||
if r.Kind == "" {
|
||||
r.Kind = "file"
|
||||
}
|
||||
if r.Lifecycle == "" {
|
||||
r.Lifecycle = ResourceLifecyclePersistent
|
||||
}
|
||||
if r.State == "" {
|
||||
r.State = ResourceStateActive
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResourceBinding connects a resource to a domain object that owns or uses it.
|
||||
type ResourceBinding struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||
ResourceID string `json:"resource_id" gorm:"type:varchar(36);not null;index"`
|
||||
TenantID uint64 `json:"tenant_id" gorm:"not null;index"`
|
||||
OwnerType string `json:"owner_type" gorm:"type:varchar(32);not null"`
|
||||
OwnerID string `json:"owner_id" gorm:"type:varchar(64);not null"`
|
||||
Relation string `json:"relation" gorm:"type:varchar(32);not null;default:'attachment'"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// TableName returns the resource binding table name.
|
||||
func (ResourceBinding) TableName() string { return "resource_bindings" }
|
||||
|
||||
// BeforeCreate assigns an ID before persistence.
|
||||
func (b *ResourceBinding) BeforeCreate(_ *gorm.DB) error {
|
||||
if b.ID == "" {
|
||||
b.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResourceAccessGrant is a revocable, expiring read capability.
|
||||
type ResourceAccessGrant struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||
TokenHash string `json:"-" gorm:"type:varchar(64);not null;uniqueIndex"`
|
||||
ResourceID string `json:"resource_id" gorm:"type:varchar(36);not null;index"`
|
||||
AccessScope string `json:"access_scope" gorm:"type:varchar(16);not null;default:'read'"`
|
||||
ExpiresAt time.Time `json:"expires_at" gorm:"not null;index"`
|
||||
RevokedAt *time.Time `json:"revoked_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// TableName returns the resource access grant table name.
|
||||
func (ResourceAccessGrant) TableName() string { return "resource_access_grants" }
|
||||
|
||||
// BeforeCreate assigns an ID before persistence.
|
||||
func (g *ResourceAccessGrant) BeforeCreate(_ *gorm.DB) error {
|
||||
if g.ID == "" {
|
||||
g.ID = uuid.NewString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildResourcePath creates a stable application resource reference.
|
||||
func BuildResourcePath(handle string) string {
|
||||
return ResourceScheme + strings.TrimSpace(handle)
|
||||
}
|
||||
|
||||
// ParseResourcePath validates and extracts a canonical resource handle.
|
||||
func ParseResourcePath(value string) (string, bool) {
|
||||
value = strings.TrimSpace(value)
|
||||
if !strings.HasPrefix(value, ResourceScheme) {
|
||||
return "", false
|
||||
}
|
||||
handle := strings.TrimPrefix(value, ResourceScheme)
|
||||
if len(handle) != ResourceHandleLength {
|
||||
return "", false
|
||||
}
|
||||
for i := 0; i < len(handle); i++ {
|
||||
if !isResourceHandleChar(handle[i]) {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return handle, true
|
||||
}
|
||||
|
||||
func isResourceHandleChar(char byte) bool {
|
||||
return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
|
||||
(char >= '0' && char <= '9') || char == '_' || char == '-'
|
||||
}
|
||||
|
||||
var resourceReferenceRE = regexp.MustCompile(
|
||||
ResourceScheme + `[A-Za-z0-9_-]{` + strconv.Itoa(ResourceHandleLength) + `}`,
|
||||
)
|
||||
|
||||
// ScanResourceReferences returns every distinct resource:// reference embedded
|
||||
// in text, in order of first appearance.
|
||||
//
|
||||
// Rich text (a chat answer, a manual knowledge document) is the only place a
|
||||
// reference's owner can be discovered after the fact: the body is what gets
|
||||
// copied between a message and a knowledge entry, so re-binding on copy starts
|
||||
// by reading the handles back out of it.
|
||||
func ScanResourceReferences(text string) []string {
|
||||
if !strings.Contains(text, ResourceScheme) {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{})
|
||||
var refs []string
|
||||
for _, span := range resourceReferenceRE.FindAllStringIndex(text, -1) {
|
||||
// A longer run of handle characters is not a 22-char handle with
|
||||
// trailing text — it is a different (invalid) token, and binding the
|
||||
// truncated prefix would attach the wrong file.
|
||||
if span[1] < len(text) && isResourceHandleChar(text[span[1]]) {
|
||||
continue
|
||||
}
|
||||
ref := text[span[0]:span[1]]
|
||||
if _, exists := seen[ref]; exists {
|
||||
continue
|
||||
}
|
||||
seen[ref] = struct{}{}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
return refs
|
||||
}
|
||||
Reference in New Issue
Block a user