Status: experimental E3 foundation
Issue: #670
Parent: #667
Depends on: E2 tenant scope (#669 / PR #690)
E2 answers:
Which tenant/project owns this resource?
E3 answers:
Is this authenticated actor/service allowed to perform this action against that exact resource and scope?
Enterprise execution must pass both checks. Scope is not authorization, and authorization cannot repair a scope mismatch.
ActorContext is normalized identity input from a trusted authentication adapter.
It distinguishes:
An actor context contains:
Issue text, WorkUnit text, provider/model output, labels, comments, prompts, and ResultManifest content are not valid identity sources.
The existing identity-binding-v0.1 contract remains provenance evidence only. It must not be promoted into authorization merely because it says an identity was verified.
v0.1 uses:
Roles are inputs from trusted identity/policy adapters. This module does not infer organization/team membership itself.
The baseline kernel evaluates:
The rule table is explicit and replaceable through AuthorizationPolicy.
The default baseline preserves IDKMesh authority boundaries:
worker/node -> execute/claim, not integrate
reviewer -> verify, not integrate
dispatcher -> dispatch, not integrate
integrator human -> integrate
owner/admin human -> policy/integration authority according to rule
provider/node -> never integrate under the baseline
An actor can hold more than one trusted role, but the requested action is checked against its exact rule and actor type.
For selected actions, effective risk at or above the rule threshold returns:
requires_approval
until a distinct eligible approver is supplied.
The baseline requires distinct approval for high/critical:
A principal cannot approve its own high-risk request.
The approver must:
The request carries an explicit approval reference so later audit instrumentation can bind the decision to the external approval/review evidence.
Policy rules may raise the effective risk of an action.
For example, manage_policy has a high risk floor. A caller cannot label a policy mutation “low” to bypass separation of duties.
The decision records both:
v0.1 uses:
public < internal < confidential < restricted
The actor and approver must have clearance at least as high as the request classification.
This is an authorization attribute check only. E5 (#672) owns external-provider/egress policy; E3 does not make a restricted external route valid.
Authorization takes an explicit evaluated_at_epoch.
The actor/approver is denied or rejected as approval evidence when:
The decision records the exact identity_revision and policy revision used. Later service caches must key/invalidate against those revisions; a stale cached decision is not permanent authority.
The request has an explicit TenantScope, and its ScopedResourceRef must carry the exact same scope.
The actor and any approver must also be bound to that exact tenant/project.
A resource from another tenant/project returns deny / scope_mismatch.
The retained adversarial fixture is:
tests/fixtures/enterprise_authz/cross-tenant-authorization.json
The kernel returns one of:
allow;deny;requires_approval.Representative stable codes include:
authorized;authorized_with_distinct_approval;scope_mismatch;actor_scope_denied;actor_unauthenticated;actor_revoked;actor_expired;actor_type_denied;role_denied;data_clearance_denied;distinct_approval_required;self_approval_forbidden;approver_scope_denied;approver_role_denied.AuthorizationDecision is auditable policy evidence.
Its machine-readable projection always declares:
{
"authority": {
"executes_action": false,
"canonical_state_write": false,
"merge": false
}
}
An allow result means the trusted policy evaluator authorizes a later
executor to attempt that bounded operation. The decision object does not
perform the operation and cannot merge code on its own.
The future E8 service request path should be:
authenticated request
-> trusted ActorContext
-> mandatory TenantScope
-> ScopedResourceRef scope match
-> AuthorizationPolicy evaluation
-> allow / deny / requires_approval
-> audit decision
-> only then side effect / secret materialization / connector call
Authorization should run again when a relevant identity or policy revision changes.
idkmesh.enterprise_identity_github (schema:
enterprise-github-identity-binding-v0.1.schema.json) is the first trusted
authentication adapter that produces an ActorContext. It composes with
this kernel rather than replacing any of its checks.
Two inputs, both already trusted before this adapter runs:
GithubActorClaims — already-authenticated GitHub actor fields (a
verified webhook’s sender_id/sender_login, or a GitHub Actions run’s
own github.actor/github.actor_id context). Issue/PR/comment title and
body text is never a claims source.GithubIdentityBindingTable — a maintainer-reviewed, versioned table
binding each trusted numeric GitHub actor id to its ActorContext roles,
tenant/project scopes, data clearance, and issuer. The numeric actor id is
the primary trust key, because a GitHub login can be renamed or re-registered
while the numeric id cannot; a login reused under a different id, or an id
whose login changed, is denied rather than trusted. This matches the
actor-id-primary pattern idkmesh.github_dispatch_authorization uses for the
separate C5 dispatch lane, which answers a different question — may this actor
dispatch — and returns a boolean decision rather than an ActorContext. No
module on main builds GithubActorClaims from a live webhook or Actions
payload yet; that producer is E3-F scope.actor_context_from_github fails closed with GithubIdentityAdapterError
for:
actor_type — a human binding backed by a bot account, or a
github_app/github_actions binding backed by a human account, is a
configuration or spoofing mismatch, not a valid identity.It does not re-check revocation or expiry itself. Those live on the
resolved ActorContext and are enforced exactly once, by this kernel’s own
authorize, so the adapter and the kernel cannot disagree about what
“expired” means.
enterprise-github-identity-binding-v0.1 field-level identity errors
(an unknown role, an invalid data clearance, …) are validated by
ActorContext itself and surface as this kernel’s own
AuthorizationContractError, not a separate error type — the binding
contract composes with ActorContext’s validation rather than
duplicating it.
This closes only the GitHub identity source. E3-D (identity/policy freshness cache integration), E3-E (audited break-glass grant), E3-F (dispatch/API enforcement integration), E3-G (two-principal acceptance fixture), and E3-H (service middleware/conformance) remain open E3 slices; E3-C (enterprise IdP/OIDC/SAML adapter) is covered separately below.
idkmesh.enterprise_identity_oidc (schema:
enterprise-oidc-identity-binding-v0.1.schema.json) is a trusted
authentication adapter that produces an ActorContext from an enterprise
identity provider reached through OIDC, SAML, or another SSO protocol. It
composes with this kernel rather than replacing any of its checks, the same
way the E3-B GitHub identity adapter does.
Two inputs, both already trusted before this adapter runs:
OidcIdentityClaims — already-verified IdP claims (an OIDC ID token’s
iss/sub/aud/iat/exp, or the equivalent normalized fields of a
verified SAML assertion). Issue/PR/comment title and body text is never a
claims source, and signature/assertion verification happens upstream —
this adapter never re-derives trust from an unverified token. No module on
main builds these claims from a live IdP response yet; that producer is
E3-F scope.OidcIdentityBindingTable — a maintainer-reviewed, versioned table
binding each trusted (issuer, subject) pair to its ActorContext roles,
tenant/project scopes, data clearance, and expected relying-party
audience. Unlike E3-B’s single numeric GitHub actor id, a subject claim is
only unique within its issuing IdP, so the composite (issuer, subject)
pair is the primary trust key: the same subject string bound under two
different issuers is two distinct identities, not a collision.actor_context_from_oidc fails closed with OidcIdentityAdapterError for:
exp, distinct
from the durable identity’s own expiry, so this check runs before any
binding lookup;issued_at_epoch are both refused rather than read as fresh;(issuer, subject) pair with no reviewed binding;audience does not match the binding’s configured
relying-party audience (a token replayed against the wrong relying
party).issuer, subject and audience are opaque IdP-controlled strings that this
adapter only ever compares, so they are compared byte for byte and a value
carrying leading or trailing whitespace is refused rather than trimmed. Trimming
would fold every leading and trailing variant of the code points Python’s
str.strip() removes — TAB, NBSP, IDEOGRAPHIC SPACE and 26 others — onto one
bound identity, and audience is this adapter’s replay check.
It does not re-check revocation, or the bound identity’s own expiry,
itself. Those live on the resolved ActorContext and are enforced exactly
once, by this kernel’s own authorize, so the adapter and the kernel cannot
disagree about what “revoked” or “expired” means for the underlying
identity — only claim freshness is this adapter’s own concern.
enterprise-oidc-identity-binding-v0.1 field-level identity errors (an
unknown role, an invalid data clearance) are validated by ActorContext
itself and surface as this kernel’s own AuthorizationContractError, not a
separate error type — the binding contract composes with ActorContext’s
validation rather than duplicating it. Structural errors in the table
document (missing or unknown fields, a wrong kind/schema_version, an
unsupported actor_type, a duplicate (issuer, subject) pair) stay with
the adapter and raise OidcIdentityAdapterError.
This closes only the enterprise IdP identity source. E3-D (identity/policy freshness cache integration), E3-E (audited break-glass grant), E3-F (dispatch/API enforcement integration, including a live claims producer for both this adapter and E3-B), E3-G (two-principal acceptance fixture), and E3-H (service middleware/conformance) remain open E3 slices.
v0.1 does not claim:
ActorContext; verifying a token/assertion against a real IdP is E3-F
scope;GithubActorClaims from a verified
webhook envelope or Actions run context is likewise E3-F scope;Those remain E3/E5/E8 integration work.