Skip to main content

AI propose-write — horizontal surface

Tier: Public
Status: Shipped (post-α H2 Wave B, 2026-05)
Audience: module authors, API maintainers, AI consultant implementors, reviewers
Related: canonical-ai-prompt-composition-surface.md, canonical-mrp-module-surface.md §10, AI-CONSULTANT.md


1. Summary

ConcernRule
Autonomous domain writesForbidden — tools with scope: "propose" create a persisted proposal only
Human confirmationRequired via POST /ai/proposals/:id/apply or dismiss via POST /ai/proposals/:id/reject
OrchestratorMust not chain apply inside the tool loop; apply is a separate HTTP action
Write exception todayrender_document (scope: "write") submits rendering jobs only

Propose-write is the first mutable-domain AI path. It returns a proposal id and human-readable diff; the chat UI surfaces Apply / Dismiss.


2. Proposal lifecycle

StatusMeaning
pendingAwaiting operator action
appliedOperator confirmed; domain mutation attempted or recorded as preview-only
rejectedOperator dismissed

3. Data model (ai_proposals)

ColumnTypeNotes
idUUIDPrimary key; returned to model and UI
workspace_idUUIDL2 isolation — all queries scoped
user_idUUIDProposer (chat turn user)
module_codetexte.g. mrp, crp
proposal_typetexte.g. orderAdjustment, scheduleAdjustment
payload_jsonJSONModule-specific diff + target entity ids
summarytextHuman-readable one-liner for chat card
statusenumpending | applied | rejected
applied_at / rejected_attimestamptzSet on terminal transitions

4. HTTP API

All routes require active workspace context (requireActiveWorkspace).

MethodPathPurpose
GET/ai/proposalsList recent proposals for workspace (member)
GET/ai/proposals/:idFetch one proposal (member, L2)
POST/ai/proposals/:id/applyHuman confirm — workspace member
POST/ai/proposals/:id/rejectDismiss — workspace member

POST /ai/chat is unchanged; proposals are created only inside propose-scope tools.

SSE event (chat stream)

{ "type": "proposal", "proposalId": "…", "moduleCode": "mrp", "proposalType": "orderAdjustment", "summary": "…" }

5. Tools (Wave B MVP)

ToolScopeModuleNotes
mrp.proposeOrderAdjustmentproposemrpTiming/qty/split suggestions; never calls write services
crp.proposeScheduleAdjustmentproposecrpCapacity/schedule hint proposals

Input/output shapes live in @umbraculum/contracts (packages/platform/contracts/src/ai/aiProposals.ts).

Apply behaviour (2026-05): When underlying MRP/CRP PATCH/write routes are read-only, apply marks the proposal applied and returns appliedPreviewOnly: true in the response envelope. Wire real domain writes when H1 2027 write workflows land.


6. UI pattern

  • Chat turn accumulates proposals[] from SSE proposal events.
  • Card shows summary, Apply, Dismiss (calls apply/reject API with session cookies).
  • Deep-link to MRP production-order page when payload_json.productionOrderId is present.

7. Maintenance


8. Worked example

Operator: “Can we push PO-1042 by two days?”

  1. Model calls mrp.proposeOrderAdjustment with { "productionOrderId": "…", "suggestedStartDate": "2026-06-02" }.
  2. Tool loads order via read service, builds diff, inserts proposal, returns { "ok": true, "proposalId": "…", "summary": "Move PO-1042 start from 2026-05-31 to 2026-06-02" }.
  3. Chat shows proposal card; operator taps Apply.
  4. API sets applied; response notes preview-only until write routes ship.