KoniKode · Software built with intention

KNOWLEDGE BASE

Documentation

DOCUMENTATION
CURRENT APPLICATIONClean Framework
DocumentationClean Framework

Clean Framework · DOCUMENTATION

Structured AI and Assisted Actions

Structured AI and Assisted Actions

Bounded candidate reference — 2026-10-20: 0.2.0-rc.1 is committed locally through 84f415b3098af5653577b2c65eea31fca21df79c, unpublished and not production-approved; stable PHP >=8.5.11 <8.6.0 is required. This is documentation, not adoption: this knowledge base retains its installed 0.1.0-rc.1 dependency and PHP 8.2.11 local profile. Dated PHP 8.2 evidence is historical; earlier dirty-source reports retain their original test provenance, not retroactive clean-commit proof. Candidate examples are illustrative: API signatures were reviewed, but candidate code was not executed by this editorial update. See current checkpoint, open criteria and adoption boundary.

Candidate file quota/circuit adapters add shared single-host admission and conservative UNKNOWN handling; they are opt-in, not distributed/paid-provider or browser-agent qualification. This KB retains its own disabled-by-default assistant and installed dependency. See qualification profile.

Phase 8 adds provider-neutral structured-generation policy and a separate Assisted Action boundary. These APIs are optional framework mechanics. They do not configure a model, supply credentials, define product prompts, grant an AI identity permissions, discover tools, or enable any route automatically.

Security model

AI output is untrusted data. It can become a stored proposal only after all of the following checks succeed:

  • application code selects an exact registered prompt purpose and version;
  • only the prompt's allowlisted untrusted input keys enter the provider call;
  • a worst-case token/cost reservation succeeds before provider work;
  • the provider is called exactly once, with no framework retry;
  • reported usage stays within the request budget;
  • output passes the application's closed, bounded schema independently;
  • application code selects an allowlisted action type; model output never selects a handler;
  • the action definition uses the same schema that validated the output;
  • the requester has the required permission and exact resource scopes;
  • proposal target scopes are a subset of the scopes carried through the AI request; and
  • prohibited credential-like parameter names are rejected recursively.

Storing a proposal does not execute it. Execution separately checks the current registered definition, permission, scopes, recent authentication when required, and enough distinct unexpired approvals bound to the exact proposal digest. The broker claims the proposal through optimistic concurrency before calling a registered handler. A handler exception becomes unknown; it is never treated as proof that an external mutation failed and is never automatically replayed.

trusted prompt registry + untrusted bounded input
                     |
                     v
          one typed provider call
                     |
                     v
     independent budget + schema validation
                     |
                     v
       stored proposal (no side effect)
                     |
       permission / scopes / step-up / approvals
                     |
                     v
     optimistic execution claim + stable key
                     |
                     v
 registered application handler -> completed | failed | unknown

Structured-generation contracts

AiPromptRegistry is populated during application composition and freezes on first resolution. There is no runtime "latest prompt" lookup. Callers identify an exact version, while only the broker/provider boundary can read trusted instructions. AiGenerationRequest carries untrusted structured input, application scopes, schema, budget, quota, and safe correlation metadata.

AiOutputSchema deliberately supports a small JSON-schema subset: closed objects; bounded arrays; bounded strings; finite numbers; integers; booleans; null; required fields; scalar enums; and numeric bounds. Root objects must set additionalProperties to false. Patterns, references, unions, custom keywords, and open objects are rejected to keep validation deterministic and avoid executing caller/provider regular expressions.

StructuredAiBroker reserves the request's worst-case input tokens, output tokens, and cost against AiBudgetReservationStore before calling a TypedStructuredAiProvider. Successful and schema-invalid responses settle to reported usage. Provider failures, over-budget responses, and unknown failures retain their worst-case reservation so repeated failures cannot bypass quota. The included reservation store is process-local and deterministic; a multi-process deployment must supply an atomic shared implementation.

UnavailableStructuredAiProvider is the safe default. The included FakeStructuredAiProvider is for tests. A production adapter remains application-owned and must normalize provider usage, preserve the trusted versus untrusted input separation, apply transport timeouts, and pass independent contract/sandbox tests.

Application-owned prompt and schema example

<?php

use Clean\Framework\Integration\Ai\AiBudget;
use Clean\Framework\Integration\Ai\AiGenerationRequest;
use Clean\Framework\Integration\Ai\AiOutputSchema;
use Clean\Framework\Integration\Ai\AiPrompt;
use Clean\Framework\Integration\Ai\AiPromptRegistry;
use Clean\Framework\Integration\Ai\AiQuota;

// Register trusted instructions during application composition, never from a
// request field or model response.
$prompts = new AiPromptRegistry();
$prompts->register(new AiPrompt(
    'content.draft',
    'v1',
    'Return only the registered draft fields.',
    ['source']
));

// The output is closed and bounded. Unknown provider fields fail validation.
$draftSchema = new AiOutputSchema('content.draft', 'v1', [
    'type' => 'object',
    'properties' => [
        'title' => ['type' => 'string', 'minLength' => 1, 'maxLength' => 100],
    ],
    'required' => ['title'],
    'additionalProperties' => false,
]);

// The controller/service supplies untrusted data and exact authorization
// scopes; it cannot replace the registered trusted instructions.
$request = new AiGenerationRequest(
    'content.draft',
    'v1',
    ['source' => $validatedSourceText],
    $draftSchema,
    new AiBudget(2_000, 300, 2_300, 15_000, 50_000, 32_768),
    new AiQuota(20, 40_000, 6_000, 1_000_000, 3_600),
    'tenant:sample',
    ['tenant:sample'],
    $correlationId
);

$generated = $structuredAiBroker->generate($request);

Assisted Action definitions and lifecycle

Every action type is registered explicitly with one parameter schema, handler, execution permission, static scopes, risk, recent-authentication rule, approval count, and optional approval permission. Registration freezes on first lookup. Risk provides a minimum floor:

RiskRecent authenticationMinimum independent approvals
lowoptional0
mediumrequired0
highrequired1
criticalrequired2

An application may set stricter requirements. An approver must have the registered approval permission, prove recent authentication, be distinct from the requester, and approve the exact action ID/content digest. Execution counts only unexpired distinct approvals and excludes the executing actor, preserving separation of duties.

Proposal states move forward only:

  • proposed may receive approvals, be rejected, expire, or be claimed;
  • executing means the handler ownership transition succeeded;
  • completed and failed are explicit handler results;
  • unknown means a side effect may have occurred and requires application-owned reconciliation;
  • rejected and expired never invoke the handler.

AssistedActionStore is an optimistic persistence port. The in-memory adapter is for one process. FileAssistedActionStore uses a private directory, per-proposal locks, atomic replacement, complete value validation, and revision compare-and-swap for one host. Multi-host applications need a database/shared adapter with genuinely atomic revision claims.

Application-owned action registration example

<?php

use Clean\Framework\AssistedAction\AssistedActionDefinition;
use Clean\Framework\AssistedAction\AssistedActionRegistry;

$actions = new AssistedActionRegistry();
$actions->register(
    new AssistedActionDefinition(
        'content.publish',
        $draftSchema,
        'content.publish',
        ['content:write'],
        'high',
        true,
        1,
        'content.approve'
    ),
    $applicationPublishHandler
);

// This creates a stored proposal only. The action type is selected by trusted
// application code, and its target cannot exceed the generation request scope.
$proposal = $assistedActions->propose(
    $generated,
    'content.publish',
    ['tenant:sample'],
    $requestingPrincipal,
    $requestAuthorization
);

// Approval and execution normally occur in separate authenticated requests.
$approved = $assistedActions->approve(
    $proposal->id(),
    $proposal->revision(),
    $independentApprover
);
$terminal = $assistedActions->execute(
    $approved->id(),
    $approved->revision(),
    $distinctExecutingPrincipal,
    $executionAuthorization
);

The handler receives an idempotency key derived from the proposal ID and immutable content digest. The application handler must forward that stable key to any provider that supports idempotency and must return an explicit AssistedActionResult. A thrown exception is intentionally converted to unknown, with the exception message excluded from proposal and audit data.

Audit and data handling

Lifecycle events contain action type, risk, revision, approval count, and scope count. They do not contain prompt instructions, model input, generated parameters, approval details, provider response IDs, credentials, or exception messages. The provider response ID is represented only by a SHA-256 digest. Applications must bind a durable AuditSink; a database application should use an outbox/transactional design appropriate to its action store.

Do not put passwords, secrets, bearer tokens, cookies, sessions, CSRF values, OAuth values, or private keys in AI input/output or action parameters. The action envelope rejects common credential-like field names, but that guard is a last boundary rather than a data-classification strategy.

Required deployment work

Before enabling a real provider or action handler, an application must:

  • keep prompts/schemas/version selection in reviewed source/configuration;
  • bind provider credentials through environment-specific secret management;
  • verify the provider adapter against its sandbox and failure semantics;
  • choose conservative request budgets and atomic shared quota storage;
  • register actions explicitly and keep action type selection out of model data;
  • bind real permissions, object/tenant scopes, recent-authentication policy, approval workflows, and durable audit;
  • use a shared atomic action store for more than one host/process;
  • make handlers idempotent where possible and build reconciliation for unknown outcomes;
  • test prompt injection, malicious/oversized output, approval races, stale revisions, scope escalation, provider timeouts, and audit/storage failure;
  • run under the supported minimum PHP version and deployment stack.

Real models/providers, provider-specific prompts, distributed quota/action stores, adversarial prompt-injection evaluation, action reconciliation jobs, the final PHP >=8.5.11 <8.6.0 provider/action deployment profile, and external side effects remain explicitly TO VERIFY. The historical PHP 8.0 target was superseded; local fakes do not qualify a live provider or deployment. Major stabilization keeps live AI/browser execution OFF; see the integration qualification boundary.

Editorial provenance

Source: docs/AI_AND_ASSISTED_ACTIONS.md. Source SHA-256: 1b350624fc47a1a79e0c39399eefbd589cea3efdaa767786b45948d7a9132927.

Framework candidate reference; editorial checkpoint 2026-10-20. See candidate status and adoption boundary.

EXPLORE FURTHER

Drag a divider or use Left and Right Arrow keys to resize. Hold Shift for larger steps. Enter hides or restores the sidebar; Home and End select its minimum or maximum width. Double-click resets the width.

Install KoniKode