93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package workbench
|
|||
|
|
|
||
|
|
import "fmt"
|
||
|
|
|
||
|
|
var legacyMetadataKeys = []string{"legacy_import", "legacy_history", "legacy_activation"}
|
||
|
|
|
||
|
|
type configurationSaveKey struct{}
|
||
|
|
|
||
|
|
type legacyImportProtection struct {
|
||
|
|
ReadOnly bool `json:"read_only"`
|
||
|
|
RequiresReview bool `json:"requires_review"`
|
||
|
|
NeedsConfiguration bool `json:"needs_configuration"`
|
||
|
|
MissingConfiguration []string `json:"missing_configuration"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Import provenance and historical output are migration-owned, opaque data.
|
||
|
|
// Typed map/task updates must retain them without treating them as executable state.
|
||
|
|
func resourceData(r *Resource, value any) JSON {
|
||
|
|
data := asJSON(value)
|
||
|
|
for _, key := range legacyMetadataKeys {
|
||
|
|
if original, exists := r.Data[key]; exists {
|
||
|
|
data[key] = original
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return data
|
||
|
|
}
|
||
|
|
|
||
|
|
func validateLegacyMetadataUpdate(original, proposed JSON) error {
|
||
|
|
for _, key := range legacyMetadataKeys {
|
||
|
|
before, existed := original[key]
|
||
|
|
after, supplied := proposed[key]
|
||
|
|
if supplied && (!existed || compact(before) != compact(after)) {
|
||
|
|
return fmt.Errorf("%w: 历史迁移标记和原始记录不可修改", ErrInvalid)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func requireLegacyExecutable(r *Resource) error {
|
||
|
|
value, exists := r.Data["legacy_import"]
|
||
|
|
if !exists {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
// Invalid protection metadata must fail closed rather than enable old jobs.
|
||
|
|
if value == nil {
|
||
|
|
return fmt.Errorf("%w: 历史迁移标记无效,请检查迁移结果", ErrInvalid)
|
||
|
|
}
|
||
|
|
decoded, err := decode[struct {
|
||
|
|
Flags *legacyImportProtection `json:"legacy_import"`
|
||
|
|
}](JSON{"legacy_import": value})
|
||
|
|
if err != nil || decoded.Flags == nil {
|
||
|
|
return fmt.Errorf("%w: 历史迁移标记无效,请检查迁移结果", ErrInvalid)
|
||
|
|
}
|
||
|
|
flags := decoded.Flags
|
||
|
|
if activation, ok := r.Data["legacy_activation"]; ok {
|
||
|
|
state, err := decode[struct {
|
||
|
|
Activation struct {
|
||
|
|
Status string `json:"status"`
|
||
|
|
} `json:"legacy_activation"`
|
||
|
|
}](JSON{"legacy_activation": activation})
|
||
|
|
if err == nil && state.Activation.Status == "active" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if flags.ReadOnly {
|
||
|
|
return fmt.Errorf("%w: 此记录为只读历史资料,原正文、质检和成稿已保留;请编辑配置并保存后继续创作", ErrInvalid)
|
||
|
|
}
|
||
|
|
if flags.RequiresReview {
|
||
|
|
return fmt.Errorf("%w: 此历史资源尚未完成配置复核,请编辑配置并保存后执行", ErrInvalid)
|
||
|
|
}
|
||
|
|
if flags.NeedsConfiguration || len(flags.MissingConfiguration) > 0 {
|
||
|
|
return fmt.Errorf("%w: 此历史资源缺少可执行配置,请先完成迁移配置核对", ErrInvalid)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func requireExecutable(r *Resource) error {
|
||
|
|
if err := requireLegacyExecutable(r); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if r.Status == "needs_configuration" {
|
||
|
|
return fmt.Errorf("%w: 资源缺少可执行配置,请重新选择并保存可用的导图、日期、画布和 Agent;历史正文、质检和成稿仍保留", ErrInvalid)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func requireActionExecutable(r *Resource, action string) error {
|
||
|
|
if r.Kind == "content-tasks" && taskActionUsesOnlySavedContent(action) {
|
||
|
|
return requireLegacyExecutable(r)
|
||
|
|
}
|
||
|
|
return requireExecutable(r)
|
||
|
|
}
|