@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.
- package/HERMES-SLACK-PROFILE-SCOPING.md +39 -24
- package/README.md +7 -5
- package/bin/sellable-install.mjs +30 -4
- package/package.json +1 -1
|
@@ -5,20 +5,18 @@ access.
|
|
|
5
5
|
|
|
6
6
|
## Default Rule
|
|
7
7
|
|
|
8
|
-
For Sellable-managed customer channels, use one
|
|
9
|
-
|
|
10
|
-
multiple active Hermes profile gateways
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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:
|
|
39
|
-
Sellable
|
|
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
|
|
124
|
-
surfaces. This customer-facing installer must not create
|
|
125
|
-
shared listener tokens
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
local-only browser state, manual SSH
|
|
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>`
|
|
126
|
-
profile's Hermes MCP env.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -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 === "--
|
|
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
|
-
? {
|
|
4150
|
-
|
|
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));
|