Skip to content

Commit 23e0045

Browse files
committed
Add connected action receipt workflow
1 parent 7b97928 commit 23e0045

12 files changed

Lines changed: 303 additions & 3 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ The Customer Desk is the ongoing communication lane. It keeps customer requests,
8787

8888
Connection and deployment steps stay approval-gated. Frontsmith prepares the path; it does not connect accounts, publish the site, or send customer messages without explicit owner approval.
8989

90+
For proposed live actions, Frontsmith can prepare a dry-run connected-action receipt before any adapter runs:
91+
92+
```bash
93+
npm run prepare:connected-action -- --action-type "email" --title "Send consultation follow-up" --target "Customer follow-up" --evidence "Owner-provided customer notes"
94+
```
95+
96+
This writes `.frontsmith/business/connected-actions/...` Markdown and JSON records with the proposed action, approval gate, Neura Registry/Relay reference placeholders, blocked adapters, and no-live-action safety boundary.
97+
9098
Frontsmith already includes the launchable website here:
9199

92100
```text
@@ -112,6 +120,7 @@ npm run owner:brief
112120
npm run prepare:reply -- --name "Customer" --project "Kitchen Remodeling" --notes "Customer wants help planning the next step."
113121
npm run prepare:estimate -- --project "Kitchen Remodeling" --scope "Cabinets, counters, lighting, and layout clarification."
114122
npm run prepare:extension -- --capability "Consultation scheduling" --connector "Google Calendar" --goal "Prepare an owner-reviewed workflow for approved consultation requests."
123+
npm run prepare:connected-action -- --action-type "email" --title "Send consultation follow-up" --target "Customer follow-up" --evidence "Owner-provided customer notes"
115124
npm run first-run:status
116125
npm run update:website
117126
npm run launch:status
@@ -164,6 +173,7 @@ Useful daily front-office commands:
164173
npm run prepare:reply -- --name "Customer" --project "Kitchen Remodeling" --notes "Customer wants help planning the next step."
165174
npm run prepare:estimate -- --project "Kitchen Remodeling" --scope "Cabinets, counters, lighting, and layout clarification."
166175
npm run prepare:extension -- --capability "Consultation scheduling" --connector "Google Calendar" --goal "Prepare an owner-reviewed workflow for approved consultation requests."
176+
npm run prepare:connected-action -- --action-type "calendar" --title "Create consultation hold" --target "Approved consultation request" --evidence "Owner-approved scheduling notes"
167177
npm run owner:brief
168178
npm run deploy:check
169179
npm test

blueprints/local-service/blueprint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"launch-plan",
2626
"activity-log",
2727
"settings-integrations",
28-
"extension-planning"
28+
"extension-planning",
29+
"connected-action-receipts"
2930
]
3031
}

docs/developer/extension-guide.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ The plan is written under `.frontsmith/business/extensions/`. It should define t
4747

4848
The default extension path must not send, publish, schedule, upload, export, or execute provider actions until the owner approves the exact live action.
4949

50+
## Connected-Action Receipt Workflow
51+
52+
Before adding a live email, publishing, file, calendar, or provider adapter, create a local receipt-style record:
53+
54+
```bash
55+
npm run prepare:connected-action -- --action-type "email" --title "Send consultation follow-up" --target "Customer follow-up" --evidence "Owner-provided customer notes"
56+
```
57+
58+
The receipt is written under `.frontsmith/business/connected-actions/` as Markdown and JSON. It records the proposed action, approval gate, Neura Registry ref placeholder, Neura Relay Action Card ref placeholder, Decision Receipt state, trace ref, blocked adapters, and safety boundary.
59+
60+
This workflow does not run Neura Relay, connect providers, send messages, publish, schedule, upload, export, or execute external actions. A real connected-mode implementation must replace the placeholder refs only after owner approval and a governed preflight.
61+
5062
## Local Testing Boundary
5163

5264
Regression tests should not erase `.frontsmith/business/`. Use `FRONTSMITH_BUSINESS_ROOT` when a test needs an isolated local business workspace.

docs/operator/start-here.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Use it to see which fields still look like Acme defaults, which business profile
6666
- Prepare estimate and proposal drafts.
6767
- Track follow-ups and next customer steps.
6868
- Prepare extension plans for tools, skills, connectors, and new workflows.
69+
- Prepare dry-run connected-action receipts before email, publishing, file, calendar, or provider actions.
6970
- Review launch readiness.
7071
- Preview the website locally.
7172
- Run the deployment readiness check.
@@ -179,6 +180,7 @@ Codex can run these tools for you:
179180
npm run prepare:reply -- --name "Customer Name" --project "Kitchen Remodeling" --notes "Customer notes"
180181
npm run prepare:estimate -- --project "Kitchen Remodeling" --scope "Scope notes"
181182
npm run prepare:extension -- --capability "Consultation scheduling" --connector "Google Calendar" --goal "Prepare an owner-reviewed workflow for approved consultation requests."
183+
npm run prepare:connected-action -- --action-type "email" --title "Send consultation follow-up" --target "Customer follow-up" --evidence "Owner-provided customer notes"
182184
npm run first-run:status
183185
npm run owner:brief
184186
npm run launch:status

docs/product/launch-scenarios.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ This document maps the public Frontsmith promise to runnable repo workflows. If
66

77
Frontsmith is acceptable for public GitHub release only when these checks pass:
88

9+
## Connected-Action Receipt
10+
11+
Scenario: the owner wants to review a proposed live action before any provider adapter runs.
12+
13+
Command:
14+
15+
```bash
16+
npm run prepare:connected-action -- --action-type "email" --title "Send consultation follow-up" --target "Customer follow-up" --evidence "Owner-provided customer notes"
17+
```
18+
19+
Expected output:
20+
21+
- local Markdown receipt under `.frontsmith/business/connected-actions/`;
22+
- local JSON receipt under `.frontsmith/business/connected-actions/`;
23+
- proposed action ref;
24+
- Neura Registry and Relay placeholder refs;
25+
- approval gate;
26+
- blocked adapters;
27+
- no live provider action.
28+
29+
Safety: this scenario must not send email, publish a website, change DNS, create calendar events, upload files, export data, configure providers, or execute connected actions.
30+
931
```bash
1032
npm run check
1133
npm run build:demo

docs/product/package-spec.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ Frontsmith should be able to prepare an owner-reviewed extension plan for a new
100100

101101
Extension planning must stay local-only. It must not connect providers, schedule appointments, send messages, publish, upload files, export data, or execute connected actions.
102102

103+
## Connected-Action Receipt Requirement
104+
105+
Frontsmith should be able to prepare a dry-run connected-action receipt before any proposed live email, publishing, file, calendar, or provider action.
106+
107+
The receipt must name the proposed action, target, evidence, approval gate, Neura Registry reference placeholder, Neura Relay Action Card reference placeholder, Decision Receipt state, trace reference, blocked adapters, and generated Markdown/JSON artifacts.
108+
109+
Connected-action receipts must stay local-only. They must not connect providers, schedule appointments, send messages, publish, upload files, export data, or execute connected actions.
110+
103111
## Default Exclusions
104112

105113
These are excluded from the v1.0 default surface:

frontsmith.config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"launch-plan",
1616
"activity-log",
1717
"settings-integrations",
18-
"extension-planning"
18+
"extension-planning",
19+
"connected-action-receipts"
1920
],
2021
"extensionCandidates": [
2122
"reviews-proof",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"prepare:reply": "node scripts/prepare-customer-reply.mjs",
2929
"prepare:estimate": "node scripts/prepare-estimate-draft.mjs",
3030
"prepare:extension": "node scripts/prepare-extension-plan.mjs",
31+
"prepare:connected-action": "node scripts/prepare-connected-action-receipt.mjs",
3132
"first-run:status": "node scripts/first-run-readiness.mjs",
3233
"launch:status": "node scripts/launch-status.mjs",
3334
"build:demo": "node scripts/build-demo.mjs",
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { mkdir, readFile, writeFile } from "node:fs/promises";
2+
import path from "node:path";
3+
import { businessPath, businessRoot } from "./lib/paths.mjs";
4+
5+
const root = businessRoot();
6+
const business = await readBusinessProfile();
7+
const actionType = readArg("--action-type") ?? "email";
8+
const title = readArg("--title") ?? "Send consultation follow-up";
9+
const target = readArg("--target") ?? "Customer follow-up";
10+
const evidence = readArg("--evidence") ?? "Owner-provided customer notes";
11+
const outputDir = businessPath("connected-actions");
12+
const slug = slugify(`${actionType}-${title}`);
13+
const markdownFile = path.join(outputDir, `${slug}-receipt.md`);
14+
const jsonFile = path.join(outputDir, `${slug}-receipt.json`);
15+
const record = connectedActionRecord({ actionType, title, target, evidence });
16+
17+
await mkdir(outputDir, { recursive: true });
18+
await writeFile(jsonFile, `${JSON.stringify(record, null, 2)}\n`);
19+
await writeFile(markdownFile, renderReceipt({ record, markdownFile, jsonFile }));
20+
21+
console.log(`Prepared connected-action receipt: ${markdownFile}`);
22+
console.log("Mode: dry-run only");
23+
console.log("Execution: blocked until owner approval and connected-mode preflight");
24+
25+
function readArg(name) {
26+
const index = process.argv.indexOf(name);
27+
if (index === -1) return null;
28+
return process.argv[index + 1] ?? null;
29+
}
30+
31+
async function readBusinessProfile() {
32+
const profilePath = businessPath("business.json");
33+
try {
34+
return JSON.parse(await readFile(profilePath, "utf8"));
35+
} catch (error) {
36+
if (error && error.code === "ENOENT") {
37+
throw new Error(`No business workspace found at ${profilePath}. Run npm run bootstrap first.`);
38+
}
39+
throw error;
40+
}
41+
}
42+
43+
function connectedActionRecord({ actionType, title, target, evidence }) {
44+
const normalizedActionType = slugify(actionType);
45+
const normalizedTitle = slugify(title);
46+
const refBase = `${normalizedActionType}-${normalizedTitle}`;
47+
48+
return {
49+
schema: "frontsmith.connected_action_receipt.v0.1",
50+
mode: "dry_run",
51+
business: {
52+
name: business.businessName,
53+
workspace: ".frontsmith/business"
54+
},
55+
proposed_action: {
56+
action_ref: `frontsmith-action-${refBase}`,
57+
type: actionType,
58+
title,
59+
target,
60+
evidence,
61+
requested_by: "owner_or_operator",
62+
execution_default: "blocked"
63+
},
64+
authority_refs: {
65+
neura_registry_ref: `registry-ref-pending-${refBase}`,
66+
neura_relay_action_card_ref: `relay-action-card-pending-${refBase}`,
67+
neura_relay_decision_receipt_ref: "pending_until_connected_mode_preflight",
68+
trace_ref: `frontsmith-trace-${refBase}`
69+
},
70+
approval_gate: {
71+
owner_approval_required: true,
72+
approval_status: "not_approved",
73+
required_before_execution: [
74+
"owner approval of the exact action",
75+
"confirmed target and payload",
76+
"confirmed connector and account",
77+
"connected-mode Neura Relay preflight when enabled",
78+
"receipt review before adapter execution"
79+
]
80+
},
81+
adapter_boundary: {
82+
live_adapter_enabled: false,
83+
blocked_adapters: ["email", "publish", "file", "calendar", "provider_setup"],
84+
reason: "Frontsmith records the proposed connected action locally before any live provider action is allowed."
85+
},
86+
created_at: new Date().toISOString()
87+
};
88+
}
89+
90+
function renderReceipt({ record, markdownFile, jsonFile }) {
91+
return `# Connected-Action Receipt
92+
93+
Business: ${record.business.name}
94+
Status: Dry run, needs owner review, not executed
95+
Generated: ${record.created_at}
96+
97+
## Executive Summary
98+
99+
Frontsmith prepared a local receipt-style record for a proposed connected action. No provider adapter ran. No email was sent, website was published, file was uploaded, calendar event was created, provider was configured, or external action was executed.
100+
101+
## Proposed Action
102+
103+
- Action ref: \`${record.proposed_action.action_ref}\`
104+
- Type: ${record.proposed_action.type}
105+
- Title: ${record.proposed_action.title}
106+
- Target: ${record.proposed_action.target}
107+
- Evidence: ${record.proposed_action.evidence}
108+
- Execution default: ${record.proposed_action.execution_default}
109+
110+
## Neura References
111+
112+
- Registry ref: \`${record.authority_refs.neura_registry_ref}\`
113+
- Relay Action Card ref: \`${record.authority_refs.neura_relay_action_card_ref}\`
114+
- Relay Decision Receipt ref: \`${record.authority_refs.neura_relay_decision_receipt_ref}\`
115+
- Trace ref: \`${record.authority_refs.trace_ref}\`
116+
117+
These refs are local placeholders until connected client mode is approved and a real Neura Relay preflight is run.
118+
119+
## Approval Gate
120+
121+
- Owner approval required: ${record.approval_gate.owner_approval_required ? "yes" : "no"}
122+
- Approval status: ${record.approval_gate.approval_status}
123+
124+
Required before execution:
125+
126+
${record.approval_gate.required_before_execution.map((item) => `- ${item}`).join("\n")}
127+
128+
## Adapter Boundary
129+
130+
- Live adapter enabled: ${record.adapter_boundary.live_adapter_enabled ? "yes" : "no"}
131+
- Blocked adapters: ${record.adapter_boundary.blocked_adapters.join(", ")}
132+
- Reason: ${record.adapter_boundary.reason}
133+
134+
## Artifacts
135+
136+
- Markdown receipt: \`${displayPath(markdownFile)}\`
137+
- JSON receipt: \`${displayPath(jsonFile)}\`
138+
- Business profile: \`${displayPath(businessPath("business.json"))}\`
139+
140+
## Safety Boundary
141+
142+
This connected-action receipt is local-only. It does not send email, publish the website, change DNS, create calendar events, upload files, export data, configure providers, or execute connected actions.
143+
`;
144+
}
145+
146+
function slugify(value) {
147+
return String(value)
148+
.toLowerCase()
149+
.replace(/&/g, " and ")
150+
.replace(/[^a-z0-9]+/g, "-")
151+
.replace(/^-+|-+$/g, "")
152+
.slice(0, 80);
153+
}
154+
155+
function displayPath(targetFile) {
156+
const normalizedFile = path.normalize(targetFile);
157+
const normalizedRoot = path.normalize(root);
158+
const relativeToBusiness = path.relative(normalizedRoot, normalizedFile);
159+
160+
if (relativeToBusiness && !relativeToBusiness.startsWith("..") && !path.isAbsolute(relativeToBusiness)) {
161+
return toPosix(path.join(".frontsmith", "business", relativeToBusiness));
162+
}
163+
164+
const relativeToProject = path.relative(process.cwd(), normalizedFile) || normalizedFile;
165+
return toPosix(relativeToProject);
166+
}
167+
168+
function toPosix(value) {
169+
return value.split(path.sep).join("/");
170+
}

tests/launch-readiness.mjs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const requiredCapabilities = [
2020
"launch-plan",
2121
"activity-log",
2222
"settings-integrations",
23-
"extension-planning"
23+
"extension-planning",
24+
"connected-action-receipts"
2425
];
2526

2627
const requiredScripts = {
@@ -30,6 +31,7 @@ const requiredScripts = {
3031
"prepare:reply": "node scripts/prepare-customer-reply.mjs",
3132
"prepare:estimate": "node scripts/prepare-estimate-draft.mjs",
3233
"prepare:extension": "node scripts/prepare-extension-plan.mjs",
34+
"prepare:connected-action": "node scripts/prepare-connected-action-receipt.mjs",
3335
"first-run:status": "node scripts/first-run-readiness.mjs",
3436
"update:website": "node scripts/update-website.mjs",
3537
"launch:status": "node scripts/launch-status.mjs",
@@ -44,7 +46,9 @@ const requiredScripts = {
4446

4547
const requiredFiles = [
4648
"scripts/prepare-extension-plan.mjs",
49+
"scripts/prepare-connected-action-receipt.mjs",
4750
"workflows/prepare-extension-plan.md",
51+
"workflows/prepare-connected-action.md",
4852
"docs/product/launch-scenarios.md",
4953
"docs/developer/extension-guide.md",
5054
"tests/regression.mjs",
@@ -100,6 +104,7 @@ for (const command of [
100104
"npm run prepare:reply",
101105
"npm run prepare:estimate",
102106
"npm run prepare:extension",
107+
"npm run prepare:connected-action",
103108
"npm run first-run:status",
104109
"npm run update:website",
105110
"npm run launch:status",
@@ -126,9 +131,12 @@ for (const doc of [
126131
assertIncludes(operatorGuide, "Prepare an extension plan", "operator extension prompt");
127132
assertIncludes(operatorGuide, "first-run-readiness.md", "operator first-run readiness output");
128133
assertIncludes(packageSpec, "Extension Requirement", "package extension requirement");
134+
assertIncludes(packageSpec, "Connected-Action Receipt Requirement", "package connected-action receipt requirement");
129135
assertIncludes(architecture, ".frontsmith/business/extensions/", "architecture extension folder");
130136
assertIncludes(extensionGuide, "Extension Planning Workflow", "developer extension workflow");
137+
assertIncludes(extensionGuide, "Connected-Action Receipt Workflow", "developer connected-action workflow");
131138
assertIncludes(launchScenarios, "must not send email, publish a website, change DNS, connect providers", "launch gate safety");
139+
assertIncludes(launchScenarios, "Connected-Action Receipt", "connected-action launch scenario");
132140

133141
console.log("Frontsmith launch-readiness contract passed.");
134142

0 commit comments

Comments
 (0)