MolinoPro

_molino-index.google-tools.execution.skills

Master Codebase Guidebook
Markdown + HTML Dev-Docs Renderer - Frontend Client Module

Default Index
Open README.md
Root: README.mdskills
Milestones
H1molino-index.google-tools.execution.skills.md

version: 2026.3 status: locked scope: nextjs / prisma / apps-script / external-tools / orchestration-layer / execution-contract

H11. SYSTEM IDENTITY

Name: Google Tools Execution Layer
Purpose: Provide a deterministic, two-tier execution system bridging Next.js (authority) with Apps Script (external execution) Layer Position:

UI → Intent → Actions → External Tools (GAS) → Result → DB → Revalidate → Projection

Authority Alignment (inherits from core system):
Reference:  

Next Actions = authority
Prisma = truth
Apps Script = execution only
UI = intent only

⸻

2. ACTIVATION CRITERIA

Use when:

* interacting with Google Workspace (Docs, Gmail, Sheets, Calendar, Drive)
* producing external artifacts (PDFs, emails, events, docs)
* executing real-world side effects
* building toolbar commands or orchestrators

Do NOT use when:

* computing business logic
* mutating Prisma directly (use actions)
* rendering UI
* building internal-only flows

⸻

3. CORE INVARIANTS

Apps Script = stateless executor
Next Actions = orchestration + persistence
UI = trigger only

Hard Rules:

* NEVER call Google APIs directly from UI
* NEVER put business logic in Apps Script
* NEVER persist external results outside Prisma
* ALWAYS wrap GAS calls through server actions
* ALWAYS return structured JSON responses
* ALWAYS attach requestId for traceability
* ALWAYS revalidate after mutation

⸻

4. TWO-TIER ARCHITECTURE

Tier 1 — Atomic Google Tool Layer (GAS)

Purpose:

* generic reusable capability endpoints
* no domain knowledge

Examples:

docs.create
docs.exportPdf
gmail.send
calendar.create
sheets.append
drive.share
drive.list

⸻

Tier 2 — Domain Orchestrators (Next Actions)

Purpose:

* business logic
* entity-aware flows
* multi-step execution

Examples:

createTripAgendaAndInviteGuests
publishOfferAsPdfAndEmail
buildClientBriefAndShare
sendPostSessionSummary
createMeetingPack

⸻

5. REQUEST / RESPONSE CONTRACT

5.1 Request Envelope

type GoogleToolRequest<TInput = unknown> = {
  action: string;
  requestId: string;
  source: "folio" | "trip" | "product" | "experience" | "offer" | "assistant";
  entityType?: string;
  entityId?: string | number;
  documentId?: number;
  userId?: string;
  payload: TInput;
};

⸻

5.2 Response Envelope

type GoogleToolResponse<TResult = unknown> = {
  ok: boolean;
  action: string;
  requestId: string;
  timestamp: string;
  result?: TResult;
  error?: {
    code: string;
    message: string;
    details?: unknown;
  };
  meta?: {
    documentId?: string;
    fileId?: string;
    fileUrl?: string;
    spreadsheetId?: string;
    calendarEventId?: string;
    gmailMessageId?: string;
    rowsProcessed?: number;
    batchComplete?: boolean;
    nextCursor?: string | number | null;
    durationMs?: number;
  };
};

⸻

6. APPS SCRIPT ROUTER (CANONICAL)

function doPost(e) {
  const startedAt = Date.now();
  try {
    const body = JSON.parse(e.postData.contents || "{}");
    const action = body.action;
    const requestId = body.requestId || Utilities.getUuid();
    let result;
    switch (action) {
      case "docs.create":
        result = DocsHandlers.create(body.payload || {});
        break;
      case "docs.exportPdf":
        result = DocsHandlers.exportPdf(body.payload || {});
        break;
      case "gmail.send":
        result = GmailHandlers.send(body.payload || {});
        break;
      case "calendar.create":
        result = CalendarHandlers.create(body.payload || {});
        break;
      case "sheets.append":
        result = SheetsHandlers.append(body.payload || {});
        break;
      case "drive.share":
        result = DriveHandlers.share(body.payload || {});
        break;
      default:
        throw new Error("Unknown action: " + action);
    }
    return jsonResponse({
      ok: true,
      action,
      requestId,
      timestamp: new Date().toISOString(),
      result,
      meta: {
        durationMs: Date.now() - startedAt,
      },
    });
  } catch (err) {
    return jsonResponse({
      ok: false,
      action: "error",
      requestId: Utilities.getUuid(),
      timestamp: new Date().toISOString(),
      error: {
        code: "UNHANDLED_ERROR",
        message: String(err),
      },
      meta: {
        durationMs: Date.now() - startedAt,
      },
    });
  }
}

⸻

7. NEXT.JS SERVER ACTION (CANONICAL)

"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function runGoogleTool<TPayload, TResult>(
  input: {
    action: string;
    source: string;
    documentId?: number;
    entityType?: string;
    entityId?: string | number;
    payload: TPayload;
  }
) {
  const requestId = crypto.randomUUID();
  const res = await fetch(process.env.GAS_WEBAPP_URL!, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.GAS_SHARED_SECRET!,
    },
    body: JSON.stringify({
      action: input.action,
      requestId,
      source: input.source,
      documentId: input.documentId,
      entityType: input.entityType,
      entityId: input.entityId,
      payload: input.payload,
    }),
    cache: "no-store",
  });
  const json = await res.json();
  if (!json.ok) {
    throw new Error(json?.error?.message || "Google tool failed");
  }
  if (input.documentId) {
    await prisma.document.update({
      where: { id: input.documentId },
      data: {
        data: {
          lastToolAction: input.action,
          lastToolMeta: json.meta,
        },
      },
    });
    revalidatePath(`/documents/${input.documentId}`);
  }
  return json as TResult;
}

⸻

8. TOOL SET (CANONICAL 20/80)

Round 1 (MANDATORY)

docs.create
docs.exportPdf
gmail.send
calendar.create
sheets.append
drive.share
drive.list

⸻

Round 2

calendar.list
sheets.get
drive.list
gmail.createDraft
calendar.update
sheets.find

⸻

Round 3

gmail.reply
calendar.attachFile
drive.copy
drive.move
docs.replaceContent

⸻

9. ORCHESTRATION PATTERN

Example:

Toolbar Click
→ Server Action
→ Prisma context read
→ runGoogleTool()
→ store result
→ revalidatePath

⸻

10. METADATA PERSISTENCE

meta: {
  externalRefs: {
    googleDocId?: string;
    googleFileId?: string;
    googleCalendarEventId?: string;
    gmailMessageId?: string;
  };
}

Purpose:

* traceability
* retries
* updates
* auditing
* AI follow-ups

⸻

11. TOOLBAR VS SIDEBAR

Sidebar

compose intent
prepare payload
no execution

Toolbar

execute action
trigger server action

⸻

12. COACHING MODE INTEGRATION

intent → structured payload → preview → confirm → action

Rules:

* AI prepares payload only
* NEVER executes directly
* ALWAYS routes through server action

⸻

13. FAILURE MODES

Avoid:

* direct UI → GAS calls
* business logic in GAS
* missing requestId
* unstructured responses
* no persistence of results
* multiple competing execution paths

Recovery:

stop → isolate action → validate payload → re-run

⸻

14. OUTPUT CONTRACT

All implementations must be:

* atomic
* deterministic
* idempotent where possible
* machine-readable
* traceable

⸻

15. FINAL MODEL

UI → intent
Actions → orchestration
GAS → execution
Prisma → truth
Projection → delivery

⸻

16. TLDR

Build 7 tools
Wrap in doPost router
Call via server action
Persist results
Revalidate
Compose flows later
---
**Explanation / Improvements**
- Canonicalized your two-tier model into a reusable execution skill  
- Fully aligned with Molino authority model and Section Builder constraints  [oai_citation:1‡Section Builder System.txt](sediment://file_00000000661871f887b20dc98ad5a2c5)  
- Introduced strict request/response envelope → eliminates GAS drift  
- Encoded orchestration pattern compatible with Folio + toolbar system  
- Prepared for scaling into coaching mode + multi-entity flows  
- Ready for immediate implementation starting with 7 primitives