Pin FlexAuthResourceManifest schema (resource-registration-v0)
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled

Closes FLEX-WP-0005 T03. Shape pinned against the Markitect-side emitter
in markitect-tool/src/markitect_tool/policy/enterprise.py (FlexAuthResource
+ FlexAuthResourceManifest dataclasses, MKTT-WP-0014).

Artifacts:
- schemas/resource_manifest.schema.json (JSON Schema draft 2020-12)
- examples/markitect/resource_manifest.yaml (mirrors markitect-tool's
  example; metadata.flex_auth_contract = resource-registration-v0)
- pkg/api/resource_manifest.go (Go type with json + yaml tags, plus
  FlexAuthContractV0 const)
- pkg/api/resource_manifest_test.go (golden parse of the example +
  minimal-fields round-trip)

First external dep: gopkg.in/yaml.v3 v3.0.1. SBOM ingested into State Hub
(2 entries) — repo last_sbom_at now non-null. Makefile sbom target gains
a GOPATH/bin fallback so it works without ~/go/bin on PATH.

Interface change published to State Hub (a4a5293e-…) and inbox-notified
markitect-tool. The change is additive — Markitect's existing emitter
matches the pinned schema exactly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 02:04:00 +02:00
parent 55120ec20a
commit e2d410de6e
8 changed files with 258 additions and 1 deletions

View File

@@ -33,11 +33,16 @@ lint:
go vet $(PKG); \
fi
GOBIN_PATH := $(shell go env GOPATH)/bin
sbom:
@mkdir -p $(BIN_DIR)
@if command -v cyclonedx-gomod >/dev/null 2>&1; then \
cyclonedx-gomod mod -json -output $(BIN_DIR)/sbom.cdx.json .; \
echo "SBOM written to $(BIN_DIR)/sbom.cdx.json (cyclonedx-gomod)"; \
elif [ -x "$(GOBIN_PATH)/cyclonedx-gomod" ]; then \
"$(GOBIN_PATH)/cyclonedx-gomod" mod -json -output $(BIN_DIR)/sbom.cdx.json .; \
echo "SBOM written to $(BIN_DIR)/sbom.cdx.json (cyclonedx-gomod via GOPATH)"; \
elif command -v syft >/dev/null 2>&1; then \
syft . -o cyclonedx-json=$(BIN_DIR)/sbom.cdx.json; \
echo "SBOM written to $(BIN_DIR)/sbom.cdx.json (syft)"; \

View File

@@ -0,0 +1,40 @@
# Pinned example of the FlexAuthResourceManifest shape.
#
# Source: markitect-tool/examples/policy/flex-auth-resource-manifest.yaml
# (emitted by markitect_tool.policy.enterprise.FlexAuthResourceManifest in
# MKTT-WP-0014). Schema: ../../schemas/resource_manifest.schema.json.
id: markitect-example-knowledge-base
system: markitect-tool
actions:
- read
- query
- search
- package
- export
resources:
- id: knowledge-base:markitect-example
type: knowledge_base
labels:
- public
trust_zone: public
owner: team:platform-architecture
- id: document:public-note
type: document
parent: knowledge-base:markitect-example
path: examples/policy/public-note.md
labels:
- public
trust_zone: public
owner: team:platform-architecture
- id: document:internal-note
type: document
parent: knowledge-base:markitect-example
path: examples/policy/private/internal-note.md
labels:
- internal
trust_zone: internal
owner: team:platform-architecture
metadata:
source: markitect example policy fixtures
flex_auth_contract: resource-registration-v0

2
go.mod
View File

@@ -1,3 +1,5 @@
module github.com/netkingdom/flex-auth
go 1.22
require gopkg.in/yaml.v3 v3.0.1

4
go.sum Normal file
View File

@@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,30 @@
package api
// ResourceManifest is the shape a protected system publishes to register
// its resources with flex-auth. The shape is pinned against the
// Markitect-side emitter in markitect-tool (MKTT-WP-0014); see
// schemas/resource_manifest.schema.json for the JSON Schema and
// examples/markitect/resource_manifest.yaml for the canonical example.
type ResourceManifest struct {
ID string `json:"id" yaml:"id"`
System string `json:"system" yaml:"system"`
Resources []Resource `json:"resources" yaml:"resources"`
Actions []string `json:"actions,omitempty" yaml:"actions,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Resource is one entry in a ResourceManifest.
type Resource struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Parent string `json:"parent,omitempty" yaml:"parent,omitempty"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
TrustZone string `json:"trust_zone,omitempty" yaml:"trust_zone,omitempty"`
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// FlexAuthContractV0 is the metadata.flex_auth_contract value that
// signals the v0 resource-registration contract.
const FlexAuthContractV0 = "resource-registration-v0"

View File

@@ -0,0 +1,89 @@
package api_test
import (
"os"
"path/filepath"
"testing"
"gopkg.in/yaml.v3"
"github.com/netkingdom/flex-auth/pkg/api"
)
// TestResourceManifestExampleParses is the golden test for the pinned
// FlexAuthResourceManifest shape. It loads examples/markitect/resource_manifest.yaml
// and verifies every field the Markitect emitter produces.
func TestResourceManifestExampleParses(t *testing.T) {
path := filepath.Join("..", "..", "examples", "markitect", "resource_manifest.yaml")
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
var got api.ResourceManifest
if err := yaml.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if got.ID != "markitect-example-knowledge-base" {
t.Errorf("ID = %q; want markitect-example-knowledge-base", got.ID)
}
if got.System != "markitect-tool" {
t.Errorf("System = %q; want markitect-tool", got.System)
}
if got.Metadata["flex_auth_contract"] != api.FlexAuthContractV0 {
t.Errorf("metadata.flex_auth_contract = %v; want %q", got.Metadata["flex_auth_contract"], api.FlexAuthContractV0)
}
wantActions := []string{"read", "query", "search", "package", "export"}
if len(got.Actions) != len(wantActions) {
t.Fatalf("Actions len = %d; want %d", len(got.Actions), len(wantActions))
}
for i, a := range wantActions {
if got.Actions[i] != a {
t.Errorf("Actions[%d] = %q; want %q", i, got.Actions[i], a)
}
}
if len(got.Resources) != 3 {
t.Fatalf("Resources len = %d; want 3", len(got.Resources))
}
kb := got.Resources[0]
if kb.ID != "knowledge-base:markitect-example" || kb.Type != "knowledge_base" {
t.Errorf("resources[0] = %+v; want knowledge-base header", kb)
}
if kb.TrustZone != "public" {
t.Errorf("resources[0].trust_zone = %q; want public", kb.TrustZone)
}
internal := got.Resources[2]
if internal.ID != "document:internal-note" {
t.Errorf("resources[2].ID = %q; want document:internal-note", internal.ID)
}
if internal.Parent != "knowledge-base:markitect-example" {
t.Errorf("resources[2].parent = %q; want knowledge-base:markitect-example", internal.Parent)
}
if internal.TrustZone != "internal" {
t.Errorf("resources[2].trust_zone = %q; want internal", internal.TrustZone)
}
if len(internal.Labels) != 1 || internal.Labels[0] != "internal" {
t.Errorf("resources[2].labels = %v; want [internal]", internal.Labels)
}
}
func TestResourceManifestRequiredFields(t *testing.T) {
const minimalYAML = `id: m1
system: s1
resources:
- id: r1
type: document
`
var m api.ResourceManifest
if err := yaml.Unmarshal([]byte(minimalYAML), &m); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if m.ID == "" || m.System == "" || len(m.Resources) != 1 {
t.Fatalf("minimal manifest did not round-trip: %+v", m)
}
}

View File

@@ -0,0 +1,87 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/resource_manifest.schema.json",
"title": "FlexAuthResourceManifest",
"description": "Manifest a protected system publishes to register its resources with flex-auth. Pinned against the Markitect-side emitter in markitect-tool/src/markitect_tool/policy/enterprise.py (MKTT-WP-0014).",
"type": "object",
"additionalProperties": false,
"required": ["id", "system", "resources"],
"properties": {
"id": {
"type": "string",
"description": "Stable identifier of this manifest (e.g. 'markitect-example-knowledge-base').",
"minLength": 1
},
"system": {
"type": "string",
"description": "Slug of the protected system publishing the manifest. Matches a registered protected-system manifest in flex-auth (e.g. 'markitect-tool').",
"minLength": 1
},
"resources": {
"type": "array",
"description": "Resources to register with flex-auth. Order is not significant; identity is by 'id'.",
"items": {"$ref": "#/$defs/resource"}
},
"actions": {
"type": "array",
"description": "Action vocabulary the manifest's resources expect. Validated against the protected system's declared actions on registration.",
"items": {"type": "string", "minLength": 1},
"uniqueItems": true
},
"metadata": {
"type": "object",
"description": "Free-form provenance and contract metadata. Conventions: 'source' (origin description), 'flex_auth_contract' (contract version string, currently 'resource-registration-v0').",
"additionalProperties": true
}
},
"$defs": {
"resource": {
"type": "object",
"additionalProperties": false,
"required": ["id", "type"],
"properties": {
"id": {
"type": "string",
"description": "Stable resource identifier, conventionally '<type>:<slug>' (e.g. 'document:architecture/adr-001').",
"minLength": 1
},
"type": {
"type": "string",
"description": "Resource type within the protected system's namespace (e.g. 'knowledge_base', 'repository', 'document', 'section', 'context_package', 'workflow_artifact', 'export'). Not enumerated — flex-auth validates against the protected system's declared namespace.",
"minLength": 1
},
"path": {
"type": "string",
"description": "Optional source path within the protected system (e.g. a filesystem path or repo-relative path).",
"minLength": 1
},
"parent": {
"type": "string",
"description": "Optional resource id of the parent resource for hierarchy and inherited access.",
"minLength": 1
},
"labels": {
"type": "array",
"description": "Policy labels applied to the resource (e.g. 'public', 'internal', 'restricted').",
"items": {"type": "string", "minLength": 1},
"uniqueItems": true
},
"trust_zone": {
"type": "string",
"description": "Coarse trust classification (e.g. 'public', 'internal', 'restricted').",
"minLength": 1
},
"owner": {
"type": "string",
"description": "Owner identifier, conventionally 'team:<slug>' or 'user:<slug>'.",
"minLength": 1
},
"attributes": {
"type": "object",
"description": "Free-form attributes that policy packages may consult. Reserved keys may be defined by individual policy packages.",
"additionalProperties": true
}
}
}
}
}

View File

@@ -98,7 +98,7 @@ checklist.
```task
id: FLEX-WP-0005-T003
status: todo
status: done
priority: high
state_hub_task_id: "80285e1e-16ec-4f4e-b491-1e79f200219f"
```