H1SKILL: Projection Layer — Apps Script & Google Workspace
Module: External Output Layer Status: Active — canonical contract defined Part of: MASTER_SKILL.md — Chapter 8 See also:
../appscript.apis.md,../appscript-office-server-firstLoop.md
H2Identity
Apps Script = stateless external office execution layer.
It does NOT:
- Own business logic
- Access Prisma
- Make decisions about entities
- Know about internal app state
It ONLY:
- Executes Google Workspace operations
- Receives fully computed payloads
- Returns structured results
Principle: Compute in Next.js → project externally via Apps Script.
H2Authority Map
| Layer | Does |
|---|---|
| Apps Script | Thin atomic actions against Google Workspace |
| Next.js server actions | Business orchestration, compute, permissions, Prisma |
| UI / toolbar | Intent launchers only — no secrets, no Google logic |
H2Canonical Request/Response Contract
H3Request (Next.js → Apps Script)
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;
};
H3Response (Apps Script → Next.js)
type GoogleToolResponse<TResult = unknown> = {
ok: boolean;
action: string;
requestId: string;
timestamp: string; // ISO string
result?: TResult;
error?: {
code: string;
message: string;
details?: unknown;
};
meta?: {
spreadsheetId?: string;
documentId?: string;
fileId?: string;
fileUrl?: string;
rowsProcessed?: number;
batchComplete?: boolean;
nextCursor?: string | number | null;
durationMs?: number;
};
};
H2Apps Script Router (doPost)
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 "coreOffice.generateTemplates":
result = CoreOfficeHandlers.generateTemplates(body.payload || {});
break;
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.createEvent":
result = CalendarHandlers.createEvent(body.payload || {});
break;
case "sheets.appendRows":
result = SheetsHandlers.appendRows(body.payload || {});
break;
// ... etc.
default:
return jsonResponse({ ok: false, error: { code: "UNKNOWN_ACTION" }, ... });
}
return jsonResponse({
ok: true, action, requestId,
timestamp: new Date().toISOString(),
result,
meta: { durationMs: Date.now() - startedAt }
});
} catch (err) {
return jsonResponse({ ok: false, error: { code: "INTERNAL", message: err.message }, ... });
}
}
H2Tier 1: Generic Tool Endpoints
H3From Next.js → GAS via API routes:
POST /api/tools/google/docs/create
POST /api/tools/google/docs/export-pdf
POST /api/tools/google/docs/append-content
POST /api/tools/google/gmail/send
POST /api/tools/google/gmail/create-draft
POST /api/tools/google/sheets/append
POST /api/tools/google/sheets/get-rows
POST /api/tools/google/calendar/create-event
POST /api/tools/google/calendar/update-event
POST /api/tools/google/drive/create-file
POST /api/tools/google/drive/share-file
POST /api/tools/google/drive/export-pdf
H2Tier 2: App-Level Orchestrators (Next Server Actions)
These call Tier 1 endpoints with entity-aware business logic:
createTripAgendaAndInviteGuests({ tripId, guestEmails });
publishOfferAsPdfAndEmail({ offerId, clientEmail });
logMeetingAndCreateFollowup({ meetingData, followupDate });
buildClientBriefAndShare({ tripId, clientInfo });
createTripDaySchedule({ tripId, days });
sendPostSessionSummary({ sessionId, attendeeEmails });
H2Output Bundle (for trip exports)
type OutputBundle = {
meta: {
entity: string; // "trip" | "offer" | "order"
entityId: string;
title: string;
};
document?: {
// for docs.create
title: string;
sections: { heading?: string; content: string }[];
};
calendar?: Array<{
// for calendar.createEvent
title: string;
startDateTime: string;
endDateTime: string;
description?: string;
location?: string;
guests?: string[];
}>;
tabular?: {
// for sheets.appendRows
sheetId?: string;
rows: Record<string, any>[];
};
};
H2Trip Projection Paths
Trip (canonical entity)
↓ computeBundle(trip)
OutputBundle
↓ projectEntity(bundle)
├── → docs.create → Generated Google Doc
├── → docs.exportPdf → PDF
├── → gmail.send → Email to client
└── → calendar.create → Calendar event + invites
H2Security Note
- Apps Script Web App must be deployed with appropriate access level
requestIdenables correlation and audit logging- Never expose the GAS web app URL in client-side code
- All calls to GAS go through Next.js server actions only
This skill is Chapter 8 of MASTER_SKILL.md.
See also: ../appscript.apis.md for full action catalog.