access-engine/examples/informed-decision-lifecycle/policy.md
tegwick b5da745063
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 1m5s
FLEX-WP-0035-T05: informed-decision lifecycle v2 declares application.access
- 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
2026-10-09 20:10:50 +02:00

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"}
}
}
```