@sellable/install 0.1.316 → 0.1.317

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.
@@ -5,20 +5,18 @@ access.
5
5
 
6
6
  ## Default Rule
7
7
 
8
- For Sellable-managed customer channels, use one shared Sellable Reply Bot
9
- Socket Mode listener and route inbound events by exact channel id. Do not run
10
- multiple active Hermes profile gateways against the same `SLACK_APP_TOKEN`.
8
+ For Sellable-managed customer channels, use one dedicated Slack app/token pair
9
+ per active Hermes customer profile. Run native `hermes -p <profile> gateway run`
10
+ for that profile only. Do not run multiple active Hermes profile gateways
11
+ against the same `SLACK_APP_TOKEN`.
11
12
 
12
- The shared dispatcher maps `slackTeamId + channelId` to `customerSlug`,
13
- `workspaceId`, `hermesProfile`, and `responsePolicy`. It then invokes
14
- `hermes -p <profile>` with profile-local `SELLABLE_CONFIG_PATH` and
15
- `SELLABLE_CONFIGS_DIR`, and posts the response back to the original Slack
16
- channel/thread.
13
+ The customer's Slack app is bound to the exact Slack channel id and the
14
+ customer's profile-local Sellable MCP config. Customer profiles must not store
15
+ or read Sellable Admin MCP credentials, admin Slack app tokens, or sibling
16
+ profile config.
17
17
 
18
- Customer profiles do not store the shared raw Slack token pair; route records
19
- store metadata and secret references only. Use dedicated per-customer Slack
20
- app/token pairs only for customer-owned Slack workspaces or strict enterprise
21
- isolation.
18
+ The shared dispatcher remains a fallback/research path only. Use it only when
19
+ Sellable Admin explicitly owns one shared listener and route registry.
22
20
 
23
21
  The 2026-07-06 Hostinger POC proved the native Hermes listener for the
24
22
  `sellable-admin` profile with the existing `Sellable Reply Bot` app in
@@ -35,8 +33,9 @@ hermes --profile acme send --to slack:C0BFERDV3N0 --subject '[Hermes acme smoke]
35
33
  ```
36
34
 
37
35
  That succeeded and is useful as an internal smoke test. It does not change the
38
- inbound listener rule: one shared dispatcher should own Socket Mode for
39
- Sellable-managed channels.
36
+ inbound listener rule: each active customer profile needs its own native Slack
37
+ app token pair unless Sellable Admin deliberately chooses the dispatcher
38
+ fallback.
40
39
 
41
40
  ## Which Slack Values Are Enough
42
41
 
@@ -114,17 +113,32 @@ SLACK_REQUIRE_MENTION=true
114
113
  Keep the file mode at `0600`. Do not copy raw tokens into planning docs, chat,
115
114
  or committed artifacts. Record token fingerprints only.
116
115
 
116
+ The profile's Hermes MCP env must also include the profile-local Sellable
117
+ config, customer workspace lock, and fail-closed guard:
118
+
119
+ ```yaml
120
+ SELLABLE_CONFIG_PATH: /srv/hermes/profiles/acme/sellable/config.json
121
+ SELLABLE_CONFIGS_DIR: /srv/hermes/profiles/acme/sellable/configs
122
+ SELLABLE_LOCK_WORKSPACE_ID: ws_acme
123
+ SELLABLE_REQUIRE_WORKSPACE_LOCK: "1"
124
+ ```
125
+
126
+ `SELLABLE_REQUIRE_WORKSPACE_LOCK=1` means an unbound customer profile fails
127
+ closed before making Sellable API calls instead of falling back to shared or
128
+ admin config state. Only the `sellable-admin` profile should provision,
129
+ inspect, or repair other profiles.
130
+
117
131
  For the shared dispatcher model, do not copy the shared `SLACK_BOT_TOKEN` or
118
132
  `SLACK_APP_TOKEN` into customer profile `.env` files. The dispatcher owns those
119
133
  tokens and route metadata decides the customer profile.
120
134
 
121
135
  ## Shared Dispatcher Ownership
122
136
 
123
- The shared Slack dispatcher and its route registry are internal Sellable Admin
124
- surfaces. This customer-facing installer must not create Slack apps, write
125
- shared listener tokens, or validate cross-customer route bundles. Sellable Admin
126
- owns those steps, then calls this installer only for profile-local Sellable
127
- runtime bootstrap inside the already-provisioned Hermes profile.
137
+ The shared Slack dispatcher and its route registry are fallback Sellable Admin
138
+ surfaces. This customer-facing installer must not create shared route bundles or
139
+ write shared listener tokens. Sellable Admin owns Slack app creation and profile
140
+ provisioning, then calls this installer for profile-local Sellable runtime
141
+ bootstrap inside the already-provisioned Hermes profile.
128
142
 
129
143
  ## Verification
130
144
 
@@ -147,8 +161,9 @@ Expected proof:
147
161
 
148
162
  ## Current Phase 03 Status
149
163
 
150
- Phase 03 has proved the profile-scoped installer path, outbound Hermes send
151
- path, and live Slack listener for the `sellable-admin` profile. Phase 03.1 adds
152
- the shared dispatcher route registry as a Sellable Admin-owned onboarding
153
- surface so managed customer channels can be onboarded through the VPS without
154
- local-only browser state, manual SSH edits, or untracked processes.
164
+ Phase 03 proved the profile-scoped installer path, outbound Hermes send path,
165
+ and live Slack listener for the `sellable-admin` profile. Phase 03.1 now targets
166
+ dedicated native Slack apps per customer profile, profile-local Sellable MCP
167
+ locks, and fail-closed customer profile behavior so managed customer channels
168
+ can be onboarded through the VPS without local-only browser state, manual SSH
169
+ edits, or untracked processes.
package/README.md CHANGED
@@ -122,11 +122,13 @@ It creates:
122
122
  /srv/hermes/profiles/acme/.env # only when Slack token inputs are supplied
123
123
  ```
124
124
 
125
- Bootstrap also writes `SELLABLE_LOCK_WORKSPACE_ID=<workspace-id>` into the
126
- profile's Hermes MCP env. Customer profiles are therefore locked to their own
127
- Sellable workspace: Sellable MCP requests are forced to that workspace,
128
- workspace switching/creation is blocked, and workspace listings are filtered.
129
- Use the `sellable-admin` profile for provisioning or cross-profile inspection.
125
+ Bootstrap also writes `SELLABLE_LOCK_WORKSPACE_ID=<workspace-id>` and
126
+ `SELLABLE_REQUIRE_WORKSPACE_LOCK=1` into the profile's Hermes MCP env.
127
+ Customer profiles are therefore locked to their own Sellable workspace:
128
+ Sellable MCP requests are forced to that workspace, workspace switching/creation
129
+ is blocked, workspace listings are filtered, and a missing lock fails closed
130
+ instead of falling back to shared/admin config. Use the `sellable-admin` profile
131
+ for provisioning or cross-profile inspection.
130
132
 
131
133
  See [`HERMES-SLACK-PROFILE-SCOPING.md`](./HERMES-SLACK-PROFILE-SCOPING.md) for
132
134
  the customer Slack isolation contract and Socket Mode token requirements.
@@ -179,6 +179,8 @@ Options:
179
179
  --server <mode> package, local, or hosted. Default: package
180
180
  --token <token> Sellable API token. Also reads SELLABLE_TOKEN.
181
181
  --workspace-id <id> Sellable workspace id. Also reads SELLABLE_WORKSPACE_ID.
182
+ --require-workspace-lock For Hermes customer profiles, fail closed unless
183
+ SELLABLE_LOCK_WORKSPACE_ID is present in MCP env.
182
184
  --api-url <url> Sellable API URL. Default: ${DEFAULT_API_URL}
183
185
  --sellable-config-path <path>
184
186
  Profile-scoped Sellable config path. Also reads SELLABLE_CONFIG_PATH.
@@ -222,7 +224,9 @@ Hermes profile bootstrap:
222
224
  exact channel id unless --slack-allowed-channels is supplied. Use
223
225
  customer-scoped Sellable credentials and customer-scoped Slack credentials for
224
226
  customer profiles; shared admin tokens are weaker isolation and should remain
225
- internal-only.
227
+ internal-only. Bootstrap writes SELLABLE_REQUIRE_WORKSPACE_LOCK=1 whenever a
228
+ workspace id is supplied, so a customer profile fails closed if the workspace
229
+ lock is later removed or not yet provisioned.
226
230
 
227
231
  Hermes shared Slack dispatcher:
228
232
  Shared listener setup and route-registry validation are internal Sellable
@@ -3517,6 +3521,9 @@ function mcpEnvForHost(host, opts) {
3517
3521
  if (opts.lockWorkspaceId) {
3518
3522
  env.SELLABLE_LOCK_WORKSPACE_ID = opts.lockWorkspaceId;
3519
3523
  }
3524
+ if (opts.requireWorkspaceLock) {
3525
+ env.SELLABLE_REQUIRE_WORKSPACE_LOCK = "1";
3526
+ }
3520
3527
  return env;
3521
3528
  }
3522
3529
 
@@ -3815,6 +3822,7 @@ function parseHermesProfileBootstrapArgs(argv) {
3815
3822
  force: false,
3816
3823
  overwriteEnv: false,
3817
3824
  allowDuplicateSlackTokens: false,
3825
+ requireWorkspaceLock: false,
3818
3826
  verbose: false,
3819
3827
  };
3820
3828
 
@@ -3853,7 +3861,9 @@ function parseHermesProfileBootstrapArgs(argv) {
3853
3861
  else if (arg === "--json") opts.json = true;
3854
3862
  else if (arg === "--force") opts.force = true;
3855
3863
  else if (arg === "--overwrite-env") opts.overwriteEnv = true;
3856
- else if (arg === "--allow-duplicate-slack-tokens") {
3864
+ else if (arg === "--require-workspace-lock") {
3865
+ opts.requireWorkspaceLock = true;
3866
+ } else if (arg === "--allow-duplicate-slack-tokens") {
3857
3867
  opts.allowDuplicateSlackTokens = true;
3858
3868
  } else if (arg === "--verbose") opts.verbose = true;
3859
3869
  else {
@@ -4111,6 +4121,8 @@ function runHermesProfileBootstrap(argv) {
4111
4121
  const slackEnvKeys = Object.entries(slackEnv)
4112
4122
  .filter(([, value]) => value)
4113
4123
  .map(([key]) => key);
4124
+ const requireWorkspaceLock =
4125
+ parsed.requireWorkspaceLock || Boolean(parsed.workspaceId);
4114
4126
  const opts = {
4115
4127
  ...parsed,
4116
4128
  host: "hermes",
@@ -4121,6 +4133,7 @@ function runHermesProfileBootstrap(argv) {
4121
4133
  tokenSource,
4122
4134
  workspaceId: parsed.workspaceId,
4123
4135
  lockWorkspaceId: parsed.workspaceId,
4136
+ requireWorkspaceLock,
4124
4137
  apiUrl: parsed.apiUrl,
4125
4138
  };
4126
4139
 
@@ -4146,8 +4159,16 @@ function runHermesProfileBootstrap(argv) {
4146
4159
  activeWorkspaceId: parsed.workspaceId || null,
4147
4160
  activeWorkspaceName: parsed.workspaceName || null,
4148
4161
  workspaceLock: parsed.workspaceId
4149
- ? { enabled: true, workspaceId: parsed.workspaceId }
4150
- : { enabled: false, workspaceId: null },
4162
+ ? {
4163
+ enabled: true,
4164
+ workspaceId: parsed.workspaceId,
4165
+ required: requireWorkspaceLock,
4166
+ }
4167
+ : {
4168
+ enabled: false,
4169
+ workspaceId: null,
4170
+ required: requireWorkspaceLock,
4171
+ },
4151
4172
  tokenPresent: Boolean(token),
4152
4173
  tokenSource,
4153
4174
  tokenFingerprint: tokenFingerprint(token),
@@ -4319,6 +4340,11 @@ function runHermesProfileBootstrap(argv) {
4319
4340
  `Manual auth pending. Run: sellable auth set <token> --workspace-id <workspace_id> --sellable-config-path ${sellableConfigPath}`
4320
4341
  );
4321
4342
  }
4343
+ if (requireWorkspaceLock && !parsed.workspaceId) {
4344
+ summary.warnings.push(
4345
+ "Sellable MCP will fail closed until sellable-admin provisions SELLABLE_LOCK_WORKSPACE_ID for this customer profile."
4346
+ );
4347
+ }
4322
4348
 
4323
4349
  if (opts.json) {
4324
4350
  console.log(JSON.stringify(summary, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.316",
3
+ "version": "0.1.317",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {