@fourier-labs/harbour 0.1.26 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -168,7 +168,7 @@ When a command refuses, the refusal names its own reason and its own fix: change
168
168
  - Identity, data and files go through \`@harbour/app-sdk\` only: \`harbour.identity.current()\`, \`harbour.data.from(table)\`, \`harbour.files.*\`. Company systems go only through \`harbour.integrations.execute\` with declared operations. Never open a database, bucket or company URL from browser code, and never add another backend, auth library or deployment config: the kit is the whole path.
169
169
  - Authentication is Harbour SSO: no login forms, no roles or ids trusted from the browser; row ownership is decided in SQL through \`current_setting('harbour.user_id', true)\`. Every route needs a signed-in person; no public routes.
170
170
  - Schema changes are SQL files in \`migrations/\` with row-level security and GRANTs to \`harbour_app_gateway\`; \`harbour dev\` and \`harbour check\` apply them.
171
- - A Slack message is sent only when the person presses an explicit Send control, with a fresh UUID \`idempotencyKey\` per press (reused only to retry that press). Never send from an effect, a timer, a queue or during checks. Consent is a user action (\`harbour.integrations.connect\`); missing consent never falls back to another account.
171
+ - A Slack message or an email (\`slack.message.post\`, \`gmail.message.send\`) is sent only when the person presses an explicit Send control, with a fresh UUID \`idempotencyKey\` per press (reused only to retry that press). Never send from an effect, a timer, a queue or during checks. Consent is a user action (\`harbour.integrations.connect\`); missing consent never falls back to another account.
172
172
  - No secrets, tokens, \`.env\` values or fetched company content in source. \`.harbour/local/\` is never committed; \`.harbour/integrations.json\` and \`.harbour/kit.lock.json\` are.
173
173
 
174
174
  ## Talking to the person
@@ -5,12 +5,13 @@ import { dirname, join, parse, resolve } from "node:path";
5
5
  import { scanWorkspace } from "../../../src/analyzer.js";
6
6
  import { isKitBundle } from "./kit-bundle.js";
7
7
  import { CliError } from "./output.js";
8
- /** Closed set: each operation has exactly one identity mode and one connection kind. Gmail is read-only and user-mode only. */
8
+ /** Closed set: each operation has exactly one identity mode and one connection kind. Gmail is user-mode only (two reads and one send as the signed-in person). */
9
9
  export const OPERATIONS = {
10
10
  "slack.channel.history": { identity: "user", kind: "saas" },
11
11
  "slack.message.post": { identity: "app", kind: "saas" },
12
12
  "gmail.thread.list": { identity: "user", kind: "saas" },
13
13
  "gmail.message.read": { identity: "user", kind: "saas" },
14
+ "gmail.message.send": { identity: "user", kind: "saas" },
14
15
  "warehouse.view.read": { identity: "app", kind: "database" }
15
16
  };
16
17
  /** Explicit reads `harbour check --integrations` may run; every other operation is a send and never runs during checks. */
@@ -91,7 +91,7 @@ export function managedBlock() {
91
91
  "This app runs on Harbour. Keep these rules; `harbour check` and the deployment pipeline enforce them.",
92
92
  "",
93
93
  "- Identity, data and files go through `@harbour/app-sdk` only: `harbour.identity.current()`, `harbour.data.from(table)`, `harbour.files.*`. Never open a database, storage bucket or company system from browser code, and never `fetch` a company URL directly.",
94
- "- Company systems (Slack, Gmail, warehouse views) are reached only through `harbour.integrations.execute(connection, {operation, resource, input})` with the connection and operation declared in `.harbour/integrations.json`. Operations are a closed set: `slack.channel.history` (user identity), `slack.message.post` (app identity), `gmail.thread.list` and `gmail.message.read` (user identity, read-only, resource `inbox`), `warehouse.view.read` (app identity). Resources are logical names, never IDs, URLs or tokens.",
94
+ "- Company systems (Slack, Gmail, warehouse views) are reached only through `harbour.integrations.execute(connection, {operation, resource, input})` with the connection and operation declared in `.harbour/integrations.json`. Operations are a closed set: `slack.channel.history` (user identity), `slack.message.post` (app identity), `gmail.thread.list`, `gmail.message.read` and `gmail.message.send` (user identity — reads and one plain-text send as the signed-in person, resource `inbox`), `warehouse.view.read` (app identity). Resources are logical names, never IDs, URLs or tokens.",
95
95
  "- `.harbour/integrations.json` starts with `\"connections\": {}` and stays that way until the app really calls a company system. Adding one is two steps: declare the connection with only the operations the app calls, then `harbour integrations request <connection> --reason \"<why>\" --app-root .` (and the same command with `--environment preview` before shipping). Every declared connection blocks the deploy until IT grants it, so a connection the app does not call is a deploy that never happens; a connection that is not declared cannot be requested at all (`CONNECTION_NOT_DECLARED`), so it never gets a grant. The file is strict JSON and cannot hold comments; the worked Slack and warehouse examples are in README.md and in the comment in `src/App.tsx`.",
96
96
  "- A Slack message is sent only after the person presses an explicit Send control; pass a fresh UUID `idempotencyKey` per send action and reuse the same key when retrying that action. Never send from an effect, a timer or a background queue, and never send during checks.",
97
97
  "- Consent is a user action: call `harbour.integrations.connect(connection)` and, when it returns `consent_required`, open `authorizationUrl`. Missing consent never falls back to another account.",
@@ -224,7 +224,7 @@ const report = await integrations().execute<{ rows: Array<{ week: string; total:
224
224
  });
225
225
  \`\`\`
226
226
 
227
- A send (\`slack.message.post\`) runs only when the person presses an explicit Send control, with a fresh UUID \`idempotencyKey\` per press — never from an effect, a timer or a check. Consent is a user action: \`integrations().connect(connection)\`, and open its \`authorizationUrl\` when it answers \`consent_required\`. Anything the app calls needs its own retained check under \`.harbour/checks/\`, and anything it stops calling loses its check in the same edit.
227
+ A send (\`slack.message.post\`, \`gmail.message.send\`) runs only when the person presses an explicit Send control, with a fresh UUID \`idempotencyKey\` per press — never from an effect, a timer or a check. Consent is a user action: \`integrations().connect(connection)\`, and open its \`authorizationUrl\` when it answers \`consent_required\`. Anything the app calls needs its own retained check under \`.harbour/checks/\`, and anything it stops calling loses its check in the same edit.
228
228
  `;
229
229
  const VITE_CONFIG = `import { defineConfig } from "vite";
230
230
  import react from "@vitejs/plugin-react";
@@ -1 +1 @@
1
- export const CLI_VERSION = "0.1.26";
1
+ export const CLI_VERSION = "0.1.27";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fourier-labs/harbour",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "Harbour productionisation helper",
5
5
  "type": "module",
6
6
  "bin": {