generated from coulomb/repo-seed
Define Markitect action vocabulary
This commit is contained in:
24
docs/markitect-action-vocabulary.md
Normal file
24
docs/markitect-action-vocabulary.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Markitect Action Vocabulary
|
||||||
|
|
||||||
|
This document defines the action vocabulary for Markitect as a flex-auth
|
||||||
|
protected system. Actions are normalized before policy evaluation so Markitect
|
||||||
|
local behavior maps cleanly to CARING capabilities and exposure modes.
|
||||||
|
|
||||||
|
| Action | Markitect policy-gateway meaning | CARING capabilities | CARING planes | Exposure modes | Decision effects |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `read` | Render or fetch one document/resource. | `View` | `Data` | `Metadata`, `Masked`, `Plaintext` | `allow`, `deny`, `redact` |
|
||||||
|
| `query` | Answer over a bounded resource set. | `ViewCollection`, `Observe` | `Data` | `Metadata`, `Aggregated`, `Masked` | `allow`, `deny`, `redact` |
|
||||||
|
| `search` | Search index or metadata across resources. | `ViewCollection`, `Observe` | `Data` | `Metadata`, `Aggregated`, `Masked` | `allow`, `deny`, `redact` |
|
||||||
|
| `package` | Build a context package from selected resources. | `Create`, `Bind`, `ViewCollection` | `Intent`, `Data` | `Metadata`, `Masked` | `allow`, `deny`, `audit_only` |
|
||||||
|
| `activate_context` | Activate a prepared context package for model/tool use. | `Use`, `Execute` | `Intent`, `Policy` | `Metadata`, `Masked` | `allow`, `deny`, `audit_only` |
|
||||||
|
| `export` | Materialize or transfer content outside Markitect. | `Export` | `Data`, `Audit` | `Exportable`, `Plaintext` | `allow`, `deny`, `audit_only` |
|
||||||
|
| `workflow_run` | Execute a workflow using Markitect resources. | `Execute`, `Operate` | `Execution`, `Data`, `Audit` | `Metadata`, `Masked`, `Plaintext` | `allow`, `deny`, `audit_only` |
|
||||||
|
| `admin` | Configure Markitect policy, identity, or resource controls. | `Configure`, `Grant`, `Revoke`, `Audit` | `Configuration`, `Identity`, `Policy`, `Audit` | `Metadata`, `Plaintext` | `allow`, `deny`, `audit_only` |
|
||||||
|
|
||||||
|
`read`, `query`, and `search` never imply `Export`. Export is separate because
|
||||||
|
it changes the exposure mode to `Exportable` and usually requires explicit
|
||||||
|
conditions such as MFA and logging.
|
||||||
|
|
||||||
|
The code-level source of truth is `internal/markitect/actions.go`. The pinned
|
||||||
|
manifest example in `examples/markitect/protected_system_manifest.yaml` mirrors
|
||||||
|
that vocabulary as protected-system action definitions.
|
||||||
@@ -3,6 +3,92 @@ name: Markitect Tool
|
|||||||
description: Markitect protected-system namespace for flex-auth.
|
description: Markitect protected-system namespace for flex-auth.
|
||||||
caring_profiles:
|
caring_profiles:
|
||||||
- caring-0.4.0-rc2
|
- caring-0.4.0-rc2
|
||||||
|
actions:
|
||||||
|
- name: read
|
||||||
|
capabilities:
|
||||||
|
- View
|
||||||
|
planes:
|
||||||
|
- Data
|
||||||
|
exposure_modes:
|
||||||
|
- Metadata
|
||||||
|
- Masked
|
||||||
|
- Plaintext
|
||||||
|
- name: query
|
||||||
|
capabilities:
|
||||||
|
- ViewCollection
|
||||||
|
- Observe
|
||||||
|
planes:
|
||||||
|
- Data
|
||||||
|
exposure_modes:
|
||||||
|
- Metadata
|
||||||
|
- Aggregated
|
||||||
|
- Masked
|
||||||
|
- name: search
|
||||||
|
capabilities:
|
||||||
|
- ViewCollection
|
||||||
|
- Observe
|
||||||
|
planes:
|
||||||
|
- Data
|
||||||
|
exposure_modes:
|
||||||
|
- Metadata
|
||||||
|
- Aggregated
|
||||||
|
- Masked
|
||||||
|
- name: package
|
||||||
|
capabilities:
|
||||||
|
- Create
|
||||||
|
- Bind
|
||||||
|
- ViewCollection
|
||||||
|
planes:
|
||||||
|
- Intent
|
||||||
|
- Data
|
||||||
|
exposure_modes:
|
||||||
|
- Metadata
|
||||||
|
- Masked
|
||||||
|
- name: activate_context
|
||||||
|
capabilities:
|
||||||
|
- Use
|
||||||
|
- Execute
|
||||||
|
planes:
|
||||||
|
- Intent
|
||||||
|
- Policy
|
||||||
|
exposure_modes:
|
||||||
|
- Metadata
|
||||||
|
- Masked
|
||||||
|
- name: export
|
||||||
|
capabilities:
|
||||||
|
- Export
|
||||||
|
planes:
|
||||||
|
- Data
|
||||||
|
- Audit
|
||||||
|
exposure_modes:
|
||||||
|
- Exportable
|
||||||
|
- Plaintext
|
||||||
|
- name: workflow_run
|
||||||
|
capabilities:
|
||||||
|
- Execute
|
||||||
|
- Operate
|
||||||
|
planes:
|
||||||
|
- Execution
|
||||||
|
- Data
|
||||||
|
- Audit
|
||||||
|
exposure_modes:
|
||||||
|
- Metadata
|
||||||
|
- Masked
|
||||||
|
- Plaintext
|
||||||
|
- name: admin
|
||||||
|
capabilities:
|
||||||
|
- Configure
|
||||||
|
- Grant
|
||||||
|
- Revoke
|
||||||
|
- Audit
|
||||||
|
planes:
|
||||||
|
- Configuration
|
||||||
|
- Identity
|
||||||
|
- Policy
|
||||||
|
- Audit
|
||||||
|
exposure_modes:
|
||||||
|
- Metadata
|
||||||
|
- Plaintext
|
||||||
resource_types:
|
resource_types:
|
||||||
- name: knowledge_base
|
- name: knowledge_base
|
||||||
scope_level: Workspace
|
scope_level: Workspace
|
||||||
|
|||||||
117
internal/markitect/actions.go
Normal file
117
internal/markitect/actions.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package markitect
|
||||||
|
|
||||||
|
import "github.com/netkingdom/flex-auth/pkg/api"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActionRead = "read"
|
||||||
|
ActionQuery = "query"
|
||||||
|
ActionSearch = "search"
|
||||||
|
ActionPackage = "package"
|
||||||
|
ActionActivateContext = "activate_context"
|
||||||
|
ActionExport = "export"
|
||||||
|
ActionWorkflowRun = "workflow_run"
|
||||||
|
ActionAdmin = "admin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActionMapping describes the Markitect policy-gateway action contract.
|
||||||
|
type ActionMapping struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Capabilities []api.Capability `json:"capabilities"`
|
||||||
|
Planes []api.Plane `json:"planes"`
|
||||||
|
ExposureModes []api.ExposureMode `json:"exposure_modes"`
|
||||||
|
AllowedEffects []api.DecisionEffect `json:"allowed_effects"`
|
||||||
|
RequiredContext []api.Condition `json:"required_context,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionVocabulary is the canonical Markitect action vocabulary.
|
||||||
|
var ActionVocabulary = []ActionMapping{
|
||||||
|
{
|
||||||
|
Action: ActionRead,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityView},
|
||||||
|
Planes: []api.Plane{api.PlaneData},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked, api.ExposureModePlaintext},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectRedact},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: ActionQuery,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityViewCollection, api.CapabilityObserve},
|
||||||
|
Planes: []api.Plane{api.PlaneData},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeAggregated, api.ExposureModeMasked},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectRedact},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: ActionSearch,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityViewCollection, api.CapabilityObserve},
|
||||||
|
Planes: []api.Plane{api.PlaneData},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeAggregated, api.ExposureModeMasked},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectRedact},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: ActionPackage,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityCreate, api.CapabilityBind, api.CapabilityViewCollection},
|
||||||
|
Planes: []api.Plane{api.PlaneIntent, api.PlaneData},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||||
|
RequiredContext: []api.Condition{api.ConditionLogged},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: ActionActivateContext,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityUse, api.CapabilityExecute},
|
||||||
|
Planes: []api.Plane{api.PlaneIntent, api.PlanePolicy},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||||
|
RequiredContext: []api.Condition{api.ConditionLogged, api.ConditionPurposeBound},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: ActionExport,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityExport},
|
||||||
|
Planes: []api.Plane{api.PlaneData, api.PlaneAudit},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeExportable, api.ExposureModePlaintext},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||||
|
RequiredContext: []api.Condition{api.ConditionMFARequired, api.ConditionLogged},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: ActionWorkflowRun,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityExecute, api.CapabilityOperate},
|
||||||
|
Planes: []api.Plane{api.PlaneExecution, api.PlaneData, api.PlaneAudit},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked, api.ExposureModePlaintext},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||||
|
RequiredContext: []api.Condition{api.ConditionLogged},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: ActionAdmin,
|
||||||
|
Capabilities: []api.Capability{api.CapabilityConfigure, api.CapabilityGrant, api.CapabilityRevoke, api.CapabilityAudit},
|
||||||
|
Planes: []api.Plane{api.PlaneConfiguration, api.PlaneIdentity, api.PlanePolicy, api.PlaneAudit},
|
||||||
|
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModePlaintext},
|
||||||
|
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||||
|
RequiredContext: []api.Condition{api.ConditionMFARequired, api.ConditionLogged},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupAction returns the canonical mapping for an action.
|
||||||
|
func LookupAction(action string) (ActionMapping, bool) {
|
||||||
|
for _, mapping := range ActionVocabulary {
|
||||||
|
if mapping.Action == action {
|
||||||
|
return mapping, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ActionMapping{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionDefinitions returns protected-system action definitions for Markitect.
|
||||||
|
func ActionDefinitions() []api.ActionDefinition {
|
||||||
|
out := make([]api.ActionDefinition, 0, len(ActionVocabulary))
|
||||||
|
for _, mapping := range ActionVocabulary {
|
||||||
|
out = append(out, api.ActionDefinition{
|
||||||
|
Name: mapping.Action,
|
||||||
|
Capabilities: append([]api.Capability(nil), mapping.Capabilities...),
|
||||||
|
Planes: append([]api.Plane(nil), mapping.Planes...),
|
||||||
|
ExposureModes: append([]api.ExposureMode(nil), mapping.ExposureModes...),
|
||||||
|
Metadata: map[string]any{
|
||||||
|
"allowed_effects": append([]api.DecisionEffect(nil), mapping.AllowedEffects...),
|
||||||
|
"required_context": append([]api.Condition(nil), mapping.RequiredContext...),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
55
internal/markitect/actions_test.go
Normal file
55
internal/markitect/actions_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package markitect_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/netkingdom/flex-auth/internal/markitect"
|
||||||
|
"github.com/netkingdom/flex-auth/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActionVocabularySeparatesReadFromExport(t *testing.T) {
|
||||||
|
read, ok := markitect.LookupAction(markitect.ActionRead)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("read action not found")
|
||||||
|
}
|
||||||
|
if slices.Contains(read.Capabilities, api.CapabilityExport) {
|
||||||
|
t.Fatalf("read capabilities = %v; must not include Export", read.Capabilities)
|
||||||
|
}
|
||||||
|
if !slices.Contains(read.ExposureModes, api.ExposureModeMetadata) ||
|
||||||
|
!slices.Contains(read.ExposureModes, api.ExposureModeMasked) ||
|
||||||
|
!slices.Contains(read.ExposureModes, api.ExposureModePlaintext) {
|
||||||
|
t.Fatalf("read exposure modes = %v; want metadata, masked, and plaintext", read.ExposureModes)
|
||||||
|
}
|
||||||
|
|
||||||
|
export, ok := markitect.LookupAction(markitect.ActionExport)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("export action not found")
|
||||||
|
}
|
||||||
|
if !slices.Contains(export.Capabilities, api.CapabilityExport) {
|
||||||
|
t.Fatalf("export capabilities = %v; want Export", export.Capabilities)
|
||||||
|
}
|
||||||
|
if !slices.Contains(export.ExposureModes, api.ExposureModeExportable) {
|
||||||
|
t.Fatalf("export exposure modes = %v; want Exportable", export.ExposureModes)
|
||||||
|
}
|
||||||
|
if !slices.Contains(export.RequiredContext, api.ConditionMFARequired) {
|
||||||
|
t.Fatalf("export required context = %v; want MFARequired", export.RequiredContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionDefinitionsMirrorVocabulary(t *testing.T) {
|
||||||
|
definitions := markitect.ActionDefinitions()
|
||||||
|
if len(definitions) != len(markitect.ActionVocabulary) {
|
||||||
|
t.Fatalf("definitions len = %d; want %d", len(definitions), len(markitect.ActionVocabulary))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, mapping := range markitect.ActionVocabulary {
|
||||||
|
definition := definitions[i]
|
||||||
|
if definition.Name != mapping.Action {
|
||||||
|
t.Fatalf("definitions[%d].Name = %q; want %q", i, definition.Name, mapping.Action)
|
||||||
|
}
|
||||||
|
if !slices.Equal(definition.Capabilities, mapping.Capabilities) {
|
||||||
|
t.Fatalf("%s capabilities = %v; want %v", mapping.Action, definition.Capabilities, mapping.Capabilities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,6 +106,15 @@ func TestMarkitectProtectedSystemNamespaceExampleParses(t *testing.T) {
|
|||||||
if len(got.ResourceTypes) != 8 {
|
if len(got.ResourceTypes) != 8 {
|
||||||
t.Fatalf("ResourceTypes len = %d; want 8", len(got.ResourceTypes))
|
t.Fatalf("ResourceTypes len = %d; want 8", len(got.ResourceTypes))
|
||||||
}
|
}
|
||||||
|
if len(got.Actions) != 8 {
|
||||||
|
t.Fatalf("Actions len = %d; want 8", len(got.Actions))
|
||||||
|
}
|
||||||
|
if got.Actions[0].Name != "read" || got.Actions[0].Capabilities[0] != api.CapabilityView {
|
||||||
|
t.Fatalf("first Action = %+v; want read/View", got.Actions[0])
|
||||||
|
}
|
||||||
|
if got.Actions[5].Name != "export" || got.Actions[5].Capabilities[0] != api.CapabilityExport {
|
||||||
|
t.Fatalf("export Action = %+v; want export/Export", got.Actions[5])
|
||||||
|
}
|
||||||
if got.ResourceTypes[0].Name != "knowledge_base" || got.ResourceTypes[0].ScopeLevel != api.ScopeLevelWorkspace {
|
if got.ResourceTypes[0].Name != "knowledge_base" || got.ResourceTypes[0].ScopeLevel != api.ScopeLevelWorkspace {
|
||||||
t.Fatalf("first ResourceType = %+v; want knowledge_base Workspace", got.ResourceTypes[0])
|
t.Fatalf("first ResourceType = %+v; want knowledge_base Workspace", got.ResourceTypes[0])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ manifest cannot be mapped cleanly enough for conformance checks.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: FLEX-WP-0003-T003
|
id: FLEX-WP-0003-T003
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "cfc78bbb-5425-4780-a860-9109df62ea37"
|
state_hub_task_id: "cfc78bbb-5425-4780-a860-9109df62ea37"
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user