9734eec63c
Generated by per-group subagents covering all 52 modules across: core (18), infrastructure (10), apps (5), interfaces (3), plugins (7), testing (9) Documentation format: - AsciiDoc (.adoc) files in docs/modules/<group>/ - PlantUML (.puml) files in docs/diagrams/ - .adoc files reference diagrams via include:: directives Each doc covers: purpose, responsibilities, non-responsibilities, key types, event flow, integration points, invariants, PlantUML diagram, known issues, and open questions.
124 lines
5.1 KiB
Plaintext
124 lines
5.1 KiB
Plaintext
= core-approvals
|
|
|
|
== purpose
|
|
|
|
Evaluates whether a proposed operation should proceed by checking active grants and session approval mode, then returning an approval decision (auto-approved or pending human review). Tracks requests, decisions, and grants as event-sourced state.
|
|
|
|
== responsibilities
|
|
|
|
* Approving or rejecting operations based on tier, grants, and approval mode
|
|
* Tracking pending approval requests and resolved decisions
|
|
* Recording active grants (time-bounded permissions scoped to session/stage/project)
|
|
* Rebuilding approval state from the event log
|
|
* Providing a no-op engine for deterministic replay
|
|
|
|
== non-responsibilities
|
|
|
|
* Does not enforce policy — policy is absolute upstream
|
|
* Does not emit events — all events are consumed from the event store
|
|
* Does not validate whether the operation itself is valid (delegated to validation)
|
|
* Does not determine execution tiers — tiers are declared by tools
|
|
|
|
== key types
|
|
|
|
=== ApprovalReducer
|
|
* **kind**: interface
|
|
* **purpose**: Transforms approval state given a stored event.
|
|
|
|
=== DefaultApprovalReducer
|
|
* **kind**: class
|
|
* **purpose**: Handles `ApprovalRequestedEvent`, `ApprovalDecisionResolvedEvent`, `ApprovalGrantCreatedEvent`, `ApprovalGrantExpiredEvent`.
|
|
|
|
=== ApprovalProjector
|
|
* **kind**: class
|
|
* **purpose**: Wraps `ApprovalReducer` as a `Projection<ApprovalState>`.
|
|
|
|
=== DefaultApprovalRepository
|
|
* **kind**: class
|
|
* **purpose**: Rebuilds `ApprovalState` for a session via `EventReplayer`.
|
|
|
|
=== ApprovalState
|
|
* **kind**: data class
|
|
* **purpose**: In-memory projection of all approval-state for a session.
|
|
* **fields**: `requests`, `decisions`, `grants`
|
|
|
|
=== DomainApprovalRequest
|
|
* **kind**: data class
|
|
* **purpose**: A request for approval of a tiered operation.
|
|
* **fields**: `id` (ApprovalRequestId), `tier`, `validationReportId`, `riskSummaryId`, `timestamp`, `causationId`, `correlationId`, `userSteering`, `toolName`, `preview`
|
|
|
|
=== ApprovalDecision
|
|
* **kind**: data class
|
|
* **purpose**: The outcome of an approval evaluation.
|
|
* **fields**: `outcome` (ApprovalOutcome), `state` (ApprovalStatus), `tier`, `contextSnapshot`, `reason`, `userSteering`
|
|
|
|
=== ApprovalGrant
|
|
* **kind**: data class
|
|
* **purpose**: A time-bounded permission that bypasses approval for matching operations.
|
|
* **fields**: `id` (GrantId), `scope` (GrantScope), `permittedTiers`, `reason`, `timestamp`, `expiresAt`
|
|
|
|
=== ApprovalContext
|
|
* **kind**: data class
|
|
* **purpose**: Snapshot of identity, mode, and causality chain at decision time.
|
|
* **fields**: `identity` (ApprovalScopeIdentity), `mode` (ApprovalMode), `causationId`, `correlationId`
|
|
|
|
=== ApprovalScopeIdentity
|
|
* **kind**: data class
|
|
* **purpose**: Identifies the scope (session, stage, project) a decision applies to.
|
|
|
|
=== ApprovalEngine
|
|
* **kind**: interface
|
|
* **purpose**: Evaluates a request against grants and approval mode to produce a decision.
|
|
|
|
=== DefaultApprovalEngine
|
|
* **kind**: class
|
|
* **purpose**: Implements the tier-based approval logic. Checks grants first, then falls through to mode-based thresholds. Returns `PENDING` for requests above the approval threshold.
|
|
|
|
=== NoOpApprovalEngine
|
|
* **kind**: class
|
|
* **purpose**: Always returns `AUTO_APPROVED` with no logic. Used during replay to avoid live approval evaluation.
|
|
|
|
== event flow
|
|
|
|
*inbound:*
|
|
* `ApprovalRequestedEvent` → creates a `DomainApprovalRequest` in state
|
|
* `ApprovalDecisionResolvedEvent` → creates an `ApprovalDecision` in state
|
|
* `ApprovalGrantCreatedEvent` → creates an `ApprovalGrant` in state
|
|
* `ApprovalGrantExpiredEvent` → removes the grant from state
|
|
|
|
*outbound:*
|
|
* None. This module is purely a projection — events are emitted by the orchestrator and consumed here.
|
|
|
|
== integration points
|
|
|
|
* `:core:events` — `StoredEvent`, event payload types, shared identity types (`SessionId`, `ApprovalRequestId`, etc.)
|
|
* `:core:sessions` — `Projection`, `EventReplayer`, `ApprovalMode`
|
|
|
|
== invariants
|
|
|
|
* `ApprovalDecision.isFinal` is true only when `state != PENDING`
|
|
* `ApprovalDecision.isApproved` is true only when outcome is `APPROVED` or `AUTO_APPROVED`
|
|
* A grant with `expiresAt <= now` is never matched by `DefaultApprovalEngine`
|
|
* `NoOpApprovalEngine` is used exclusively on the replay path — never during live execution
|
|
* Request, decision, and grant IDs are globally unique; maps in `ApprovalState` are indexed by these IDs
|
|
|
|
== PlantUML diagram
|
|
|
|
[plantuml, core-approvals, "png"]
|
|
----
|
|
include::../../diagrams/core-approvals.puml[]
|
|
----
|
|
|
|
|
|
|
|
|
|
== known issues
|
|
|
|
* `ApprovalDecision.id` is nullable in the data class but `DefaultApprovalReducer` always assigns a non-null ID; `NoOpApprovalEngine` returns `null` for the ID, which could make downstream consumers handle both cases unnecessarily.
|
|
* `ApprovalContext` construction in `DefaultApprovalReducer` hardcodes `ApprovalMode.PROMPT` and null stage/project IDs because the event does not carry this information. The full context is only available at evaluation time.
|
|
|
|
== open questions
|
|
|
|
* Should `ApprovalContext` be embedded in the event payload rather than reconstructed during reduction?
|
|
* The `RiskSummaryId` on `DomainApprovalRequest` references data from the validation module — the two modules are separate and share no common risk model type.
|