- application.access allows only with purpose=readback and the authoring human bar; no workload path, no audience or client-scoped claims - v1 operations unchanged; pin.json moves to v2; live pin stays v1 until T04 - examples/application-readback: readback catalogue with informed-decision's key-cape client binding; integration test over the shipped files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 609675@bnt-lap001 Assistant-Session: 4987a36e-eb38-4b10-803c-15a29cd78212
224 lines
7.7 KiB
Markdown
224 lines
7.7 KiB
Markdown
---
|
|
id: informed-decision.lifecycle
|
|
name: Decision lifecycle authoring and collaboration
|
|
namespace: informed-decision:decision-memo
|
|
version: v2
|
|
status: ready
|
|
package: flexauth.informed_decision.lifecycle
|
|
allow_ttl: 30s
|
|
actions: [lifecycle.register, lifecycle.read, lifecycle.search, lifecycle.revise, lifecycle.source, lifecycle.evaluation, lifecycle.comment, lifecycle.events, lifecycle.packet, lifecycle.publish, lifecycle.request_review, lifecycle.outcomes, application.access]
|
|
owner: flex-auth
|
|
fixtures: [fixtures.json]
|
|
caring:
|
|
profile: caring-0.4.0-rc2
|
|
enforce: false
|
|
activation:
|
|
mode: local
|
|
---
|
|
|
|
# Initial lifecycle audience
|
|
|
|
Founder direction, 2026-10-04, INFD-WP-0007: net-kingdom-admins humans and
|
|
system:serviceaccount:state-hub:state-hub. This package is evaluated by Access
|
|
Engine; the consumer supplies authenticated identity and immutable record facts.
|
|
The evaluator's workload caller remains informed-decision/review, audience
|
|
flex-auth. The State Hub subject is authenticated separately for audience
|
|
informed-decision-lifecycle using CA-verified TokenReview, never a browser token.
|
|
|
|
Humans require KeyCape AAL2 MFA within 12 hours for authoring. Actual human binding
|
|
retains the separate review policy's 900-second MFA requirement. This package
|
|
has no accept action. An initial caller can register and search; record access
|
|
requires original authorship or an explicit publication audience. Published
|
|
collaborators may research, discuss and refine. Publication and requesting review
|
|
require original authorship or an admitted human collaborator. Publication may
|
|
name only the initial human group; explicit subjects still require admitted
|
|
identity. Review routing is fixed to State Hub, decision-record class and
|
|
net-kingdom-admins. Every operation is fresh; neither content nor evaluation
|
|
assertions confer permission. Command digests correlate commands, not signatures.
|
|
No presentation/view hash enters this policy.
|
|
|
|
The sibling registry declares identity contract profiles, following the existing
|
|
review packages. These are not real human or workload identifiers and mint no
|
|
identity. Authentication-derived values still come from the enforced consumer
|
|
boundary (KeyCape sessions or CA-verified TokenReview). This remains a consumer
|
|
claim trust boundary; a registry declaration is not independent verification.
|
|
|
|
## Application access readback (v2)
|
|
|
|
v2 adds one action, `application.access`, for the advisory readback that tells
|
|
user-engine whether to list informed-decision for a signed-in person
|
|
(`FLEX-WP-0035-T05`, `FLEX-DEC-2026-019`). v1's twelve operations are
|
|
unchanged.
|
|
|
|
The rule is the same `human` bar authoring uses: net-kingdom-admins, KeyCape
|
|
AAL2 `pwd`+`otp` within 12 hours, tenant platform. It reads only realm-level
|
|
IAM Profile v0.3 claims (groups and normalized `assurance`). It reads no
|
|
audience, authorized party or client-scoped role, because the forwarded
|
|
claims were issued for user-engine's client. The workload path is excluded:
|
|
it turns on `authentication_audience`, and the readback lists applications for
|
|
people. It also requires `purpose == "readback"`, so it can never allow on an
|
|
enforcement pin, which refuses a purpose-bearing request. The allow grants
|
|
nothing inside informed-decision; every operation is still checked above.
|
|
|
|
```rego
|
|
import rego.v1
|
|
|
|
common if {
|
|
input.tenant == "tenant:platform"
|
|
input.subject.tenant == input.tenant
|
|
input.resource.tenant == input.tenant
|
|
input.resource.system == "informed-decision"
|
|
input.context.contract == "informed-decision.lifecycle.v1"
|
|
regex.match("^sha256:[0-9a-f]{64}$", input.context.command_digest)
|
|
input.subject.attributes.principal_type_source == "authentication-derived"
|
|
input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"}
|
|
}
|
|
|
|
human if {
|
|
input.subject.type == "human"
|
|
"net-kingdom-admins" in input.subject.attributes.groups
|
|
assurance := input.subject.attributes.assurance
|
|
assurance.level == "aal2"
|
|
assurance.mfa == true
|
|
assurance.source == "key-cape"
|
|
assurance.methods == ["pwd", "otp"]
|
|
is_number(assurance.at)
|
|
age := time.now_ns()/1000000000 - assurance.at
|
|
age >= -30
|
|
age <= 43200
|
|
}
|
|
|
|
workload if {
|
|
input.subject.type == "service"
|
|
input.subject.id == "system:serviceaccount:state-hub:state-hub"
|
|
input.subject.attributes.authentication_audience == "informed-decision-lifecycle"
|
|
is_string(input.subject.attributes.workload_uid)
|
|
input.subject.attributes.workload_uid != ""
|
|
}
|
|
|
|
identity if human
|
|
identity if workload
|
|
|
|
memo_resource if {
|
|
input.resource.type == "decision-memo"
|
|
regex.match("^memo:[A-Za-z0-9._:-]{1,256}$", input.resource.id)
|
|
}
|
|
|
|
owner if {
|
|
input.context.record.registered == true
|
|
some author in [input.context.record.registered_by, input.context.record.revision_by]
|
|
author.sub == input.subject.id
|
|
author.kind == "person"
|
|
human
|
|
}
|
|
owner if {
|
|
input.context.record.registered == true
|
|
some author in [input.context.record.registered_by, input.context.record.revision_by]
|
|
author.sub == input.subject.id
|
|
author.kind == "service"
|
|
workload
|
|
}
|
|
|
|
readable if owner
|
|
readable if {
|
|
input.context.record.registered == true
|
|
input.subject.id in input.context.record.published_audience.subjects
|
|
}
|
|
readable if {
|
|
human
|
|
"net-kingdom-admins" in input.context.record.published_audience.groups
|
|
}
|
|
|
|
may_publish if owner
|
|
may_publish if {
|
|
human
|
|
readable
|
|
}
|
|
|
|
audience_ok if {
|
|
a := input.context.publication_audience
|
|
is_array(a.subjects)
|
|
is_array(a.groups)
|
|
count(a.subjects) <= 32
|
|
count(a.groups) <= 32
|
|
count(a.subjects)+count(a.groups) > 0
|
|
every group in a.groups { group == "net-kingdom-admins" }
|
|
every subject in a.subjects { is_string(subject); subject != ""; subject != "*" }
|
|
}
|
|
|
|
operation if {
|
|
input.action == "lifecycle.register"
|
|
memo_resource
|
|
not input.context.record.registered
|
|
input.context.memo_version == 1
|
|
}
|
|
operation if {
|
|
input.action == "lifecycle.register"
|
|
memo_resource
|
|
owner
|
|
input.context.memo_version == 1
|
|
}
|
|
operation if {
|
|
input.action == "lifecycle.search"
|
|
input.resource.type == "decision-collection"
|
|
input.resource.id == "decisions"
|
|
}
|
|
operation if {
|
|
input.action in {"lifecycle.read", "lifecycle.events", "lifecycle.packet", "lifecycle.outcomes",
|
|
"lifecycle.revise", "lifecycle.source", "lifecycle.evaluation", "lifecycle.comment"}
|
|
memo_resource
|
|
readable
|
|
}
|
|
operation if {
|
|
input.action == "lifecycle.publish"
|
|
memo_resource
|
|
may_publish
|
|
audience_ok
|
|
}
|
|
operation if {
|
|
input.action == "lifecycle.request_review"
|
|
memo_resource
|
|
may_publish
|
|
routing := input.context.review_routing
|
|
routing.submitter == "state-hub"
|
|
routing.memo_class == "decision-record"
|
|
routing.review_group == "net-kingdom-admins"
|
|
}
|
|
|
|
application_access if {
|
|
input.action == "application.access"
|
|
input.purpose == "readback"
|
|
input.tenant == "tenant:platform"
|
|
input.subject.tenant == input.tenant
|
|
input.resource.system == "informed-decision"
|
|
input.resource.type == "application"
|
|
input.resource.id == "informed-decision"
|
|
human
|
|
}
|
|
|
|
decision := {"effect":"allow", "reason":"application_access_readback"} if {
|
|
application_access
|
|
} else := {"effect":"allow", "reason":"lifecycle_collaboration"} if {
|
|
common
|
|
identity
|
|
operation
|
|
} else := {"effect":"deny", "reason":"lifecycle_scope_or_identity_refused"}
|
|
```
|
|
|
|
```rego test
|
|
package flexauth.informed_decision.lifecycle_test
|
|
import rego.v1
|
|
import data.flexauth.informed_decision.lifecycle
|
|
|
|
test_empty_denied if {
|
|
lifecycle.decision.effect == "deny" with input as {}
|
|
}
|
|
|
|
test_application_access_needs_readback_purpose if {
|
|
lifecycle.decision.effect == "deny" with input as {
|
|
"action": "application.access", "tenant": "tenant:platform",
|
|
"subject": {"type": "human", "tenant": "tenant:platform"},
|
|
"resource": {"id": "informed-decision", "type": "application", "system": "informed-decision"}
|
|
}
|
|
}
|
|
```
|