UAPI GLM 5.2 Identity Gate Scaffold Plan 2026-06-22
GLM 5.2 stepwise Identity Gate scaffold plan aligned to Codex contract decisions v0.2.
Promotion Metadata
- Source feedback id:
134 - Source feedback ids:
134 - Promotion request id:
65 - Feedback category:
beneficial - Feedback source:
[REDACTED:high-entropy] - Feedback created at:
2026-06-22T18:32:59.083663+00:00 - Target slug:
[REDACTED:high-entropy] - Review state:
review - Reviewer note: AI lane auto-promoted redacted feedback into internal catalog context.
Source Feedback Body
Feedback 134
- Product:
uapi - Category:
beneficial - Related slug:
[REDACTED:high-entropy] - Source:
[REDACTED:high-entropy] - Created at:
2026-06-22T18:32:59.083663+00:00
GLM 5.2 — Identity Gate Scaffold Plan
Date: 2026-06-22 Lane: Unified login / role gates (first wedge) Sources: codex-contract-decisions-v0-2, glm52-auth-role-gate-test-matrix, glm52-job-lifecycle-state-machine. Assumptions: Codex v0.2 role set and lane mapping approved. Mock principals only.
Purpose
Stepwise scaffold for Identity Gate: first gate in job lifecycle. Checks received → identity_gated | denied by validating role_assertions against requested lane.
Inputs / Outputs
Inputs: UnifiedIdentityPrincipal, requested lane, existing LoginSession (for replay/expiry checks).
Allow: LoginSession + GateRecord(allow) + EventEnvelope.
Deny: GateRecord(deny) + EventEnvelope. No session created.
Lane Authorization Table (Codex v0.2)
operator → embedding, light-inference, foundry-demo
viewer → (none — read-only)
service-provider → embedding
agent-dispatcher → embedding, light-inference
device-endpoint → device-local
hardware-researcher → (none — research/diary only)
Scaffold Steps
Step 1: Principal Validation
fn validate_principal(p) -> Result<(), DenyReason> {
if p.role_assertions.is_empty() { return Deny("missing role_assertions") }
for role in p.role_assertions {
if role not in APPROVED_ROLES { return Deny("unknown role: {role}") }
}
Ok(())
}
Covers T10 (missing roles), T7 (unknown role).
Step 2: Lane Authorization
fn authorize_lane(roles, lane) -> Result<(), DenyReason> {
for role in roles {
if LANE_MAP[role].contains(lane) { return Ok(()) }
}
Deny("role not authorized for lane {lane}")
}
Covers T5 (viewer→embedding), T6 (service→light-inference).
Step 3: Session Lifecycle Checks
fn check_session(session, requested_lane) -> Result<(), DenyReason> {
match session {
None => Ok(()),
Some(s) => {
if now() > s.expires_at { return Deny("session expired") }
if s.lane != requested_lane { return Deny("lane mismatch") }
Ok(())
}
}
}
Covers T8 (expired replay), T9 (lane mismatch).
Step 4: Session Creation (allow path only)
fn create_session(principal, lane) -> LoginSession {
LoginSession {
session_id: uuid(), principal_ref: principal.principal_id,
lane, issued_at: now(), expires_at: now() + 15min,
correlation_id: uuid(),
}
}
Step 5: GateRecord + EventEnvelope Emission
Every Identity Gate evaluation produces both records with threaded correlation_id. Gate fields: gate_name="identity", decision=allow|deny, reason. Event fields: from_state="received", to_state="identity_gated"|"denied".
Step 6: Lifecycle Integration
Identity Gate owns received → identity_gated and received → denied only. On allow, job moves to identity_gated, handoff to Placement Gate. On deny, job is terminal.
Test Coverage
| Test | Step | Result |
|---|---|---|
| T1 operator→embedding | 1+2+4 | allow |
| T2 service→embedding | 1+2+4 | allow |
| T3 agent→light-inference | 1+2+4 | allow |
| T4 device→device-local | 1+2+4 | allow |
| T5 viewer→embedding | 2 | deny |
| T6 service→light-inference | 2 | deny |
| T7 hardware-researcher | 2 | deny |
| T8 expired session | 3 | deny |
| T9 lane mismatch | 3 | deny |
| T10 missing roles | 1 | deny |
Non-Goals
No real auth, no Placement/Acceptance Gate logic, no event bus, no retry.
Blockers
evidence_refstorage undefined (Codex).- Mock principal fixtures needed from Worker D.
Next Handoff
→ GLM Worker D: mock principal fixtures + negative ResultPacket examples. → Codex: approve scaffold as v0.2-aligned before implementation.