This commit is contained in:
@@ -71,10 +71,14 @@ type ParseOptions struct {
|
||||
}
|
||||
|
||||
var Presets = map[string]Preset{
|
||||
"no-ai": {ID: "no-ai", Extent: "none", Review: "editorial"},
|
||||
"research": {ID: "research", Extent: "assisted", Activities: []string{"research"}, Review: "editorial"},
|
||||
"summary": {ID: "summary", Extent: "assisted", Activities: []string{"summarisation"}, Review: "editorial"},
|
||||
"full": {ID: "full", Extent: "full", Activities: []string{"generation"}, Review: "editorial"},
|
||||
// Human review is deliberately not presumed by presets. Under Article 50(4)
|
||||
// AI Act, substantive human review/editorial control can affect whether
|
||||
// certain public-interest text must be labelled. The factual review process
|
||||
// must therefore be selected explicitly by the publisher.
|
||||
"no-ai": {ID: "no-ai", Extent: "none", Review: "none"},
|
||||
"research": {ID: "research", Extent: "assisted", Activities: []string{"research"}, Review: "none"},
|
||||
"summary": {ID: "summary", Extent: "assisted", Activities: []string{"summarisation"}, Review: "none"},
|
||||
"full": {ID: "full", Extent: "full", Activities: []string{"generation"}, Review: "none"},
|
||||
}
|
||||
|
||||
var validExtents = map[string]bool{"none": true, "assisted": true, "partial": true, "mostly": true, "full": true}
|
||||
@@ -134,7 +138,8 @@ func NewFromQueryWithOptions(values url.Values, contextURL string, options Parse
|
||||
extent = "assisted"
|
||||
}
|
||||
if review == "" {
|
||||
review = "editorial"
|
||||
// Do not infer a legally relevant human-review process from AI usage alone.
|
||||
review = "none"
|
||||
}
|
||||
assurance := clean(values.Get("assurance"), 32)
|
||||
if assurance == "" {
|
||||
@@ -150,11 +155,8 @@ func NewFromQueryWithOptions(values url.Values, contextURL string, options Parse
|
||||
}
|
||||
componentReview := clean(values.Get(name+"Review"), 24)
|
||||
if componentReview == "" {
|
||||
if componentExtent == "none" {
|
||||
componentReview = "none"
|
||||
} else {
|
||||
componentReview = "editorial"
|
||||
}
|
||||
// Human review must be stated explicitly; never infer editorial control.
|
||||
componentReview = "none"
|
||||
}
|
||||
componentActivities := splitCSV(values.Get(name + "Activities"))
|
||||
if len(componentActivities) == 0 {
|
||||
@@ -252,9 +254,14 @@ func Validate(d Declaration) error {
|
||||
problems = append(problems, "declaredAt must be RFC3339")
|
||||
}
|
||||
}
|
||||
if d.EditorialResponsibility != nil && d.EditorialResponsibility.URL != "" {
|
||||
if u, err := url.ParseRequestURI(d.EditorialResponsibility.URL); err != nil || u.Scheme == "" || u.Host == "" {
|
||||
problems = append(problems, "editorialResponsibility.url must be an absolute URL")
|
||||
if d.EditorialResponsibility != nil {
|
||||
if strings.TrimSpace(d.EditorialResponsibility.Name) == "" {
|
||||
problems = append(problems, "editorialResponsibility.name is required when editorialResponsibility is present")
|
||||
}
|
||||
if d.EditorialResponsibility.URL != "" {
|
||||
if u, err := url.ParseRequestURI(d.EditorialResponsibility.URL); err != nil || u.Scheme == "" || u.Host == "" {
|
||||
problems = append(problems, "editorialResponsibility.url must be an absolute URL")
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, c := range d.Components {
|
||||
|
||||
@@ -3,6 +3,7 @@ package declaration
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -19,6 +20,9 @@ func TestPresetQuery(t *testing.T) {
|
||||
if d.Language != "fr" {
|
||||
t.Fatalf("unexpected language: %s", d.Language)
|
||||
}
|
||||
if c.HumanReview != "none" {
|
||||
t.Fatalf("preset inferred human review=%q", c.HumanReview)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoAIRejectsActivities(t *testing.T) {
|
||||
@@ -72,8 +76,8 @@ func TestArticleDeclarationFromQuery(t *testing.T) {
|
||||
if d.Components["text"].HumanReview != "none" {
|
||||
t.Fatalf("no-AI component review=%q, want none", d.Components["text"].HumanReview)
|
||||
}
|
||||
if d.Components["research"].HumanReview != "editorial" {
|
||||
t.Fatalf("AI-assisted component review=%q, want editorial default", d.Components["research"].HumanReview)
|
||||
if d.Components["research"].HumanReview != "none" {
|
||||
t.Fatalf("AI-assisted component review=%q, want no inferred review", d.Components["research"].HumanReview)
|
||||
}
|
||||
if got := d.Components["research"].Activities; len(got) != 1 || got[0] != "research" {
|
||||
t.Fatalf("research activities=%v", got)
|
||||
@@ -112,3 +116,13 @@ func TestAssuranceFromQuery(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditorialResponsibilityRequiresName(t *testing.T) {
|
||||
_, err := NewFromQuery(url.Values{
|
||||
"preset": {"research"},
|
||||
"responsibleUrl": {"https://example.org/impressum"},
|
||||
}, "https://example.org/context")
|
||||
if err == nil || !strings.Contains(err.Error(), "editorialResponsibility.name is required") {
|
||||
t.Fatalf("expected editorial responsibility name validation error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user