11 KiB
Research — Private AI Execution & Policy Foundation
Date: 2026-04-27
Spec: spec.md
This document resolves planning unknowns and records the repo-backed decisions that keep Spec 248 narrow.
Decision 1 — Reuse workspace settings for AI policy truth
Decision: Store workspace AI posture as a workspace setting at ai.policy_mode on the existing WorkspaceSettings page, with validation registered through SettingsRegistry and persistence/audit handled by SettingsWriter.
Rationale:
- The repo already has a singleton workspace settings surface, a central settings registry, and an audited writer path.
- Reusing that stack preserves workspace ownership and avoids inventing a second admin surface or a new AI persistence table.
- The existing workspace settings capabilities already separate view and manage permissions.
Evidence:
- WorkspaceSettings already owns the
/admin/settings/workspacesingleton route and usesCapabilities::WORKSPACE_SETTINGS_VIEW/Capabilities::WORKSPACE_SETTINGS_MANAGE. - SettingsRegistry is the canonical place for setting definitions and validation.
- SettingsWriter already persists workspace settings and records
workspace_setting.updated/workspace_setting.resetaudit events.
Alternatives considered:
- Add a dedicated
workspace_ai_policiestable.- Rejected: new persisted truth is unnecessary for a single workspace-owned mode and would violate the narrow v1 scope.
- Hide AI posture in environment config or feature flags.
- Rejected: not workspace-owned, not operator-auditable, and not compatible with the product requirement for explicit workspace policy.
Decision 2 — Reuse the existing operational-controls path for the runtime stop
Decision: Add ai.execution to OperationalControlCatalog, evaluate it through OperationalControlEvaluator, and expose it only on the existing Controls page under the current /system panel.
Rationale:
- The repo already has a platform-only control-center pattern with confirmation, scope previews, and audit logging.
- Reusing it avoids a second AI-specific emergency-stop mechanism or a new system AI console.
- The platform plane auth guard and capability checks are already in place for this page.
Evidence:
- Controls already owns confirmation-protected pause/resume actions and history for operational controls.
- OperationalControlCatalog is the existing source of control keys, labels, and supported scopes.
- OperationalControlEvaluator is the existing runtime lookup path.
- SystemPanelProvider and PlatformCapabilities already enforce the
/systemplane andplatform.ops.controls.managecapability.
Alternatives considered:
- Add an AI-specific console or admin page under
/system.- Rejected: duplicates the existing ops-controls pattern and broadens v1 without adding new product truth.
- Use a deploy-time environment flag as the emergency stop.
- Rejected: not operator-owned, not auditable, and not aligned with the current control-center workflow.
Decision 3 — Treat v1 as a governed decision boundary, not an AI provider runtime
Decision: The new AI seam should be an in-process governed decision boundary that accepts a registered use-case request and returns an allow/block decision plus audit-ready metadata. It must not include provider adapters, outbound model execution, queue orchestration, or result persistence in this slice.
Rationale:
- The spec explicitly avoids direct external provider calls with tenant data,
OperationRunsemantics, result persistence, and a broad marketplace. - The repo has no existing AI execution layer, so the smallest safe first step is the allow/block contract itself.
- A decision-first seam is enough to stop local provider calls from appearing feature by feature.
Evidence:
- There is no app-level AI support namespace in
apps/platform/app/**today. - Existing shared seams cover settings, ops controls, audit, product knowledge, and support diagnostics, but none of them own AI allow/block semantics.
Alternatives considered:
- Add feature-local AI helpers in product knowledge and diagnostics first.
- Rejected: duplicates policy, provider-class, and data-classification rules across surfaces.
- Build a full provider abstraction layer now.
- Rejected: speculative architecture before the first concrete provider runtime is even in scope.
Decision 4 — Lock v1 to two approved internal-only use cases and derive them from existing seams
Decision: Keep the v1 catalog locked to exactly two use cases:
product_knowledge.answer_draft, anchored to ContextualHelpResolver and its code-owned knowledge sourcesupport_diagnostics.summary_draft, anchored to SupportDiagnosticBundleBuilder as a derived summary path
Rationale:
- These are the two named likely adopters from the spec and both already exist as internal-only seams.
- Limiting the catalog to two concrete consumers satisfies ABSTR-001 while still proving the shared decision vocabulary is reusable.
- Open-ended catalog growth would silently widen scope into a general AI platform.
Evidence:
- ContextualHelpResolver already exposes
knowledgeSource()for code-owned product knowledge. - SupportDiagnosticBundleBuilder already produces the diagnostics data family used from the tenant dashboard and the tenantless operation viewer.
Alternatives considered:
- Allow any caller to register arbitrary AI use cases at runtime.
- Rejected: creates speculative platform scope and weakens governance.
- Ship only one adopter in v1.
- Rejected: the safety justification for the central catalog is stronger with the two real future consumers already identified by the spec.
Decision 5 — Support diagnostics input must be a derived redacted summary, not the raw bundle
Decision: support_diagnostics.summary_draft should consume a derived redacted summary of the support-diagnostics bundle, not the raw sections array or the raw provider/context payloads already present in the bundle structure.
Rationale:
- The current support-diagnostics bundle is broad, structured, and designed for operator inspection, not AI transport.
- Passing the raw bundle would violate the explicit v1 ban on raw provider payloads, customer-confidential data, and raw evidence excerpts.
- A derived summary keeps the AI boundary honest: if the summary cannot be produced safely, the use case should stay blocked.
Evidence:
- SupportDiagnosticBundleBuilder currently produces a rich
sectionsstructure plus contextual help and redaction notes, not a purpose-built AI summary.
Alternatives considered:
- Feed the full support-diagnostics bundle into AI with field-level filtering.
- Rejected: still too broad for v1, easier to get wrong, and unnecessary for the first governed foundation slice.
Decision 6 — Reuse the existing audit pipeline and keep the AI audit family minimal
Decision: Reuse WorkspaceAuditLogger and the underlying AuditActionId / AuditRecorder path. Keep workspace policy mutations on the existing workspace_setting.updated / workspace_setting.reset actions and add one bounded AI decision action ID for governed decision evaluations with structured metadata only.
Rationale:
- Policy changes already flow through the workspace settings audit path and should not create a second mutation pattern.
- AI decision evaluations need a stable audit record, but the narrowest shape is one action ID plus metadata, not a full AI run ledger.
- The spec explicitly bans raw prompt, raw source payload, and output persistence.
Evidence:
- SettingsWriter already logs workspace-setting updates and resets.
- WorkspaceAuditLogger already records workspace-scoped and tenant-scoped audit entries.
- AuditActionId is the canonical action registry.
Alternatives considered:
- Add a dedicated AI audit table or prompt history store.
- Rejected: violates the v1 no-new-persistence constraint and imports a second source of truth.
- Split AI decisions into many action IDs (
allowed,blocked,control_blocked, etc.).- Rejected for v1: one bounded decision action plus metadata is the smaller audit family.
Decision 7 — Keep proof narrow: unit + feature + architecture guard
Decision: Prove the slice with narrow unit tests for the decision matrix, focused feature tests for the two existing operator surfaces, and one architecture guard that fails if direct AI-provider access appears outside the governed boundary.
Rationale:
- Unit coverage is the cheapest place to prove the allow/block matrix.
- Feature coverage is still needed because the slice touches the existing workspace settings and system controls surfaces.
- Browser and heavy-governance workflows would add cost without proving additional v1 truth.
Evidence:
- Existing settings and operational-controls tests already show the repo prefers focused Pest feature tests plus targeted unit tests over browser coverage for this class of work.
Alternatives considered:
- Add browser smoke coverage in v1.
- Rejected: unnecessary for the narrow foundation slice and not the cheapest proof.
- Reuse the broad
WorkspaceSettingsManageTest.phpfamily as the primary proof.- Rejected: it is workflow-heavy and should not become the default proving lane for a narrow AI policy field.