@sellable/install 0.1.656-wip.phase189.20260815.1 → 0.1.656

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.
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.656-wip.phase189.20260815.1
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.656
7
7
  ARG MCP_PACKAGE=@sellable/mcp@0.1.880
8
8
  ARG INSTALLER_INTEGRITY
9
9
  ARG MCP_INTEGRITY=sha512-+Fo8aQlYduLaQMQff+ZA25LeUhWZ7I7UH5DECpND84LQ+rHSjKeKFj4Zs93bVHXwQCIy2OLbr75y4f4+CyT57w==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.656-wip.phase189.20260815.1" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.656" \
12
12
  && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.880" \
13
13
  && test -n "${INSTALLER_INTEGRITY}" \
14
14
  && test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.656-wip.phase189.202608
23
23
  && mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
24
24
  && npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
25
25
  && npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
26
- && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.656-wip.phase189.20260815.1' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.880') throw new Error('package identity rejected');" \
26
+ && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.656' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.880') throw new Error('package identity rejected');" \
27
27
  && node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
28
- && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.656-wip.phase189.20260815.1', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.880', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
28
+ && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.656', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.880', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
29
29
  && npm cache clean --force
30
30
 
31
31
  COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
@@ -3,7 +3,7 @@
3
3
  This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
4
4
 
5
5
  Build from this directory after
6
- `@sellable/install@0.1.656-wip.phase189.20260815.1` is
6
+ `@sellable/install@0.1.656` is
7
7
  published:
8
8
 
9
9
  ```sh
@@ -193,7 +193,7 @@ function writeBundle(outputRoot, result) {
193
193
  chmodSync(join(outputRoot, "manifest.json"), 0o444);
194
194
  }
195
195
 
196
- /** Build the one canonical kind bundle. No identity-specific digest is imported. */
196
+ /** Build the one canonical kind bundle. Its compiled digest is the image pin. */
197
197
  export function buildDefaultProfileBundle({
198
198
  kind,
199
199
  fragmentPaths,
@@ -1,30 +1,16 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import {
3
- chmodSync,
4
- closeSync,
5
- constants,
6
- existsSync,
7
- fstatSync,
8
- fsyncSync,
9
- lstatSync,
10
- mkdirSync,
11
- openSync,
12
- readFileSync,
13
- readdirSync,
14
- realpathSync,
15
- renameSync,
16
- rmSync,
17
- writeFileSync,
3
+ chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync,
4
+ mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync,
5
+ rmSync, writeFileSync,
18
6
  } from "node:fs";
19
7
  import { basename, dirname, isAbsolute, join } from "node:path";
20
8
 
21
9
  import { withProfileSoulLock } from "./fly-soul-bridge.mjs";
22
10
  import { ensureHermesNamedCron } from "./hermes-memory-snapshot.mjs";
23
11
 
24
- export const DEFAULT_PROFILE_RECONCILE_RECEIPT =
25
- ".sellable-agent/default-profile-reconcile.json";
26
- export const DEFAULT_PROFILE_RECONCILE_SCHEMA =
27
- "sellable-agent-default-profile-reconcile/v1";
12
+ export const DEFAULT_PROFILE_RECONCILE_RECEIPT = ".sellable-agent/default-profile-reconcile.json";
13
+ export const DEFAULT_PROFILE_RECONCILE_SCHEMA = "sellable-agent-default-profile-reconcile/v1";
28
14
 
29
15
  const BUNDLE_SCHEMA = "sellable-agent-default-profile-bundle/v1";
30
16
  const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
@@ -33,16 +19,12 @@ const SHA256 = /^[a-f0-9]{64}$/;
33
19
  const FENCE = /^[1-9][0-9]{0,19}$/;
34
20
  const STAGE_PREFIX = ".sellable-default-stage-";
35
21
  const MAX_BYTES = 1024 * 1024;
36
- const ADMIN_MATERIALIZER_ANCHOR = "skills/sellable-admin/SKILL.md";
37
- const ADMIN_NAMESPACE_RECEIPT_SCHEMA =
38
- "sellable-admin-skill-namespace-receipt/v1";
39
22
 
40
23
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
41
24
  const canonicalJson = (value) => {
42
25
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
43
26
  if (value && typeof value === "object") {
44
- return `{${Object.keys(value)
45
- .sort()
27
+ return `{${Object.keys(value).sort()
46
28
  .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
47
29
  .join(",")}}`;
48
30
  }
@@ -69,7 +51,8 @@ export function buildDefaultProfileProjection({
69
51
  bundleVersion: Number(
70
52
  env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION ?? ""
71
53
  ),
72
- bundleDigest: env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST ?? "",
54
+ bundleDigest:
55
+ env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST ?? "",
73
56
  };
74
57
  if (
75
58
  !["CUSTOMER", "ADMIN"].includes(kind) ||
@@ -88,14 +71,14 @@ export function buildDefaultProfileProjection({
88
71
  ) {
89
72
  throw new Error("default_profile_bundle_identity_rejected");
90
73
  }
91
- const verifiedFixture =
92
- fixtureTarget ??
74
+ const verifiedFixture = fixtureTarget ??
93
75
  (env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_TEST_ONLY === "1"
94
76
  ? {
95
77
  testOnly: true,
96
78
  workspaceId:
97
79
  env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_WORKSPACE_ID ?? "",
98
- agentId: env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_AGENT_ID ?? "",
80
+ agentId:
81
+ env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_AGENT_ID ?? "",
99
82
  }
100
83
  : undefined);
101
84
  const exactFixture =
@@ -132,7 +115,11 @@ export function bindDefaultProfileProbeCron({
132
115
  ) {
133
116
  throw new Error("default_profile_cron_binding_rejected");
134
117
  }
135
- const scriptPath = join(profileRoot, "scripts", "sellable-default-probe.sh");
118
+ const scriptPath = join(
119
+ profileRoot,
120
+ "scripts",
121
+ "sellable-default-probe.sh"
122
+ );
136
123
  const binding = {
137
124
  operationId: activeProjection.operationId,
138
125
  bootId,
@@ -169,23 +156,13 @@ PY
169
156
 
170
157
  function readRegular(path, maxBytes = MAX_BYTES) {
171
158
  const link = lstatSync(path);
172
- if (
173
- link.isSymbolicLink() ||
174
- !link.isFile() ||
175
- link.nlink !== 1 ||
176
- link.size > maxBytes
177
- )
159
+ if (link.isSymbolicLink() || !link.isFile() || link.nlink !== 1 || link.size > maxBytes)
178
160
  throw new Error("default_profile_file_rejected");
179
161
  const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
180
162
  try {
181
163
  const opened = fstatSync(fd);
182
- if (
183
- !opened.isFile() ||
184
- opened.dev !== link.dev ||
185
- opened.ino !== link.ino ||
186
- opened.nlink !== 1 ||
187
- opened.size !== link.size
188
- ) {
164
+ if (!opened.isFile() || opened.dev !== link.dev || opened.ino !== link.ino ||
165
+ opened.nlink !== 1 || opened.size !== link.size) {
189
166
  throw new Error("default_profile_file_rejected");
190
167
  }
191
168
  return readFileSync(fd);
@@ -194,123 +171,9 @@ function readRegular(path, maxBytes = MAX_BYTES) {
194
171
  }
195
172
  }
196
173
 
197
- function adminSkillNamespaceReceiptHash(profileRoot) {
198
- const namespaceRoot = join(profileRoot, "skills", "sellable-admin");
199
- if (
200
- !existsSync(namespaceRoot) ||
201
- lstatSync(namespaceRoot).isSymbolicLink() ||
202
- !lstatSync(namespaceRoot).isDirectory() ||
203
- realpathSync(namespaceRoot) !== namespaceRoot
204
- ) {
205
- throw new Error("default_profile_admin_skill_namespace_rejected");
206
- }
207
- const names = readdirSync(namespaceRoot).sort();
208
- if (names.length < 1 || names.length > 64) {
209
- throw new Error("default_profile_admin_skill_namespace_rejected");
210
- }
211
- let totalBytes = 0;
212
- const entries = names.map((name) => {
213
- if (!MANAGED_ID.test(name)) {
214
- throw new Error("default_profile_admin_skill_namespace_rejected");
215
- }
216
- const directory = join(namespaceRoot, name);
217
- if (
218
- lstatSync(directory).isSymbolicLink() ||
219
- !lstatSync(directory).isDirectory() ||
220
- realpathSync(directory) !== directory
221
- ) {
222
- throw new Error("default_profile_admin_skill_namespace_rejected");
223
- }
224
- const relativePath = `skills/sellable-admin/${name}/SKILL.md`;
225
- const content = readRegular(join(directory, "SKILL.md"), 64 * 1024);
226
- totalBytes += content.byteLength;
227
- if (totalBytes > MAX_BYTES) {
228
- throw new Error("default_profile_admin_skill_namespace_rejected");
229
- }
230
- return { path: relativePath, sha256: sha256(content) };
231
- });
232
- return sha256(
233
- canonicalJson({
234
- schemaVersion: ADMIN_NAMESPACE_RECEIPT_SCHEMA,
235
- entries,
236
- })
237
- );
238
- }
239
-
240
- function materializerReceiptHash(profileRoot, path) {
241
- const target = join(profileRoot, ...path.split("/"));
242
- if (path === ADMIN_MATERIALIZER_ANCHOR && !existsSync(target)) {
243
- return adminSkillNamespaceReceiptHash(profileRoot);
244
- }
245
- return sha256(readRegular(target, 64 * 1024));
246
- }
247
-
248
- export function buildDefaultProfileMaterializerReceipt({
249
- profileId,
250
- profileRoot,
251
- materializerReceipts,
252
- } = {}) {
253
- const allowed = new Set([
254
- "SOUL.md",
255
- "skills/sellable/SKILL.md",
256
- ADMIN_MATERIALIZER_ANCHOR,
257
- ]);
258
- if (
259
- !ID.test(profileId ?? "") ||
260
- !isAbsolute(profileRoot ?? "") ||
261
- !Array.isArray(materializerReceipts) ||
262
- materializerReceipts.length < 1 ||
263
- materializerReceipts.length > allowed.size ||
264
- new Set(materializerReceipts).size !== materializerReceipts.length ||
265
- materializerReceipts.some((path) => !allowed.has(path))
266
- ) {
267
- throw new Error("default_profile_materializer_receipt_rejected");
268
- }
269
- return {
270
- profileId,
271
- fileHashes: Object.fromEntries(
272
- materializerReceipts.map((path) => [
273
- path,
274
- materializerReceiptHash(profileRoot, path),
275
- ])
276
- ),
277
- };
278
- }
279
-
280
- export function verifyDefaultProfileMaterializerReceipt({
281
- profileRoot,
282
- materializerReceipts,
283
- materializerReceipt,
284
- } = {}) {
285
- if (
286
- !ID.test(materializerReceipt?.profileId ?? "") ||
287
- !materializerReceipt?.fileHashes ||
288
- Array.isArray(materializerReceipt.fileHashes)
289
- ) {
290
- return false;
291
- }
292
- try {
293
- const current = buildDefaultProfileMaterializerReceipt({
294
- profileId: materializerReceipt.profileId,
295
- profileRoot,
296
- materializerReceipts,
297
- });
298
- return materializerReceipts.every(
299
- (path) =>
300
- SHA256.test(materializerReceipt.fileHashes[path] ?? "") &&
301
- materializerReceipt.fileHashes[path] === current.fileHashes[path]
302
- );
303
- } catch {
304
- return false;
305
- }
306
- }
307
-
308
174
  function atomicWrite(path, bytes, mode) {
309
175
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
310
- const stage = join(
311
- dirname(path),
312
- `${STAGE_PREFIX}${basename(path)}-${process.pid}-${randomUUID()}`
313
- );
176
+ const stage = join(dirname(path), `${STAGE_PREFIX}${basename(path)}-${process.pid}-${randomUUID()}`);
314
177
  writeFileSync(stage, bytes, { flag: "wx", mode });
315
178
  chmodSync(stage, mode);
316
179
  const fd = openSync(stage, constants.O_RDONLY);
@@ -323,23 +186,16 @@ function atomicWrite(path, bytes, mode) {
323
186
  }
324
187
 
325
188
  function readBundle(bundleRoot) {
326
- if (
327
- !isAbsolute(bundleRoot ?? "") ||
328
- !existsSync(bundleRoot) ||
329
- lstatSync(bundleRoot).isSymbolicLink() ||
330
- !lstatSync(bundleRoot).isDirectory() ||
331
- realpathSync(bundleRoot) !== bundleRoot
332
- ) {
189
+ if (!isAbsolute(bundleRoot ?? "") || !existsSync(bundleRoot) ||
190
+ lstatSync(bundleRoot).isSymbolicLink() || !lstatSync(bundleRoot).isDirectory() ||
191
+ realpathSync(bundleRoot) !== bundleRoot) {
333
192
  throw new Error("default_profile_bundle_rejected");
334
193
  }
335
- const manifest = JSON.parse(
336
- readRegular(join(bundleRoot, "manifest.json")).toString("utf8")
337
- );
194
+ const manifest = JSON.parse(readRegular(join(bundleRoot, "manifest.json")).toString("utf8"));
338
195
  if (
339
196
  manifest?.schemaVersion !== BUNDLE_SCHEMA ||
340
197
  !["CUSTOMER", "ADMIN"].includes(manifest.kind) ||
341
- !Number.isSafeInteger(manifest.bundleVersion) ||
342
- manifest.bundleVersion < 1 ||
198
+ !Number.isSafeInteger(manifest.bundleVersion) || manifest.bundleVersion < 1 ||
343
199
  !SHA256.test(manifest.bundleDigest ?? "") ||
344
200
  !Array.isArray(manifest.materializerReceipts) ||
345
201
  !Array.isArray(manifest.files)
@@ -347,10 +203,9 @@ function readBundle(bundleRoot) {
347
203
  throw new Error("default_profile_bundle_rejected");
348
204
  }
349
205
  const files = manifest.files.map((entry) => {
350
- const expectedPath =
351
- entry?.type === "skill"
352
- ? `skills/sellable-defaults/${entry.id}/SKILL.md`
353
- : `crons/${entry?.name}.json`;
206
+ const expectedPath = entry?.type === "skill"
207
+ ? `skills/sellable-defaults/${entry.id}/SKILL.md`
208
+ : `crons/${entry?.name}.json`;
354
209
  if (
355
210
  !MANAGED_ID.test(entry?.id ?? "") ||
356
211
  !["skill", "cron"].includes(entry.type) ||
@@ -359,47 +214,37 @@ function readBundle(bundleRoot) {
359
214
  entry.mode !== "0444" ||
360
215
  entry.path !== expectedPath ||
361
216
  !SHA256.test(entry.sha256 ?? "") ||
362
- (entry.type === "cron" &&
363
- (!MANAGED_ID.test(entry.name ?? "") ||
364
- !entry.name.startsWith("sellable-default-")))
217
+ (entry.type === "cron" && (!MANAGED_ID.test(entry.name ?? "") ||
218
+ !entry.name.startsWith("sellable-default-")))
365
219
  ) {
366
220
  throw new Error("default_profile_bundle_rejected");
367
221
  }
368
- const content = readRegular(
369
- join(bundleRoot, ...entry.path.split("/")),
370
- 64 * 1024
371
- );
222
+ const content = readRegular(join(bundleRoot, ...entry.path.split("/")), 64 * 1024);
372
223
  if (sha256(content) !== entry.sha256)
373
224
  throw new Error("default_profile_bundle_rejected");
374
225
  return { ...entry, content: content.toString("utf8") };
375
226
  });
376
227
  const { bundleDigest, ...unsignedManifest } = manifest;
377
- if (
378
- sha256(canonicalJson({ manifest: unsignedManifest, files })) !==
379
- bundleDigest
380
- )
228
+ if (sha256(canonicalJson({ manifest: unsignedManifest, files })) !== bundleDigest)
381
229
  throw new Error("default_profile_bundle_rejected");
382
230
  return { manifest, files };
383
231
  }
384
232
 
385
233
  function activeEntries(bundle, projection) {
386
- if (
387
- bundle.manifest.kind !== projection.kind ||
388
- bundle.manifest.bundleVersion !== projection.bundleVersion ||
389
- bundle.manifest.bundleDigest !== projection.bundleDigest
390
- ) {
234
+ if (bundle.manifest.kind !== projection.kind ||
235
+ bundle.manifest.bundleVersion !== projection.bundleVersion ||
236
+ bundle.manifest.bundleDigest !== projection.bundleDigest) {
391
237
  throw new Error("default_profile_projection_mismatch");
392
238
  }
393
239
  const target = projection.fixtureTarget;
394
- const fixtureAllowed =
395
- target?.testOnly === true &&
240
+ const fixtureAllowed = target?.testOnly === true &&
396
241
  target.workspaceId === projection.workspaceId &&
397
242
  target.agentId === projection.agentId;
398
243
  return bundle.files
399
244
  .filter((entry) => entry.kind !== "FIXTURE" || fixtureAllowed)
400
245
  .sort(
401
- (left, right) =>
402
- (left.type === "skill" ? 0 : 1) - (right.type === "skill" ? 0 : 1) ||
246
+ (left, right) => (left.type === "skill" ? 0 : 1) -
247
+ (right.type === "skill" ? 0 : 1) ||
403
248
  left.path.localeCompare(right.path)
404
249
  );
405
250
  }
@@ -408,8 +253,8 @@ function validateProjection(value) {
408
253
  return Boolean(
409
254
  value &&
410
255
  ["CUSTOMER", "ADMIN"].includes(value.kind) &&
411
- ["profileId", "workspaceId", "agentId", "operationId"].every((key) =>
412
- ID.test(value[key] ?? "")
256
+ ["profileId", "workspaceId", "agentId", "operationId"].every(
257
+ (key) => ID.test(value[key] ?? "")
413
258
  ) &&
414
259
  Number.isSafeInteger(value.authorityGeneration) &&
415
260
  value.authorityGeneration > 0 &&
@@ -460,12 +305,8 @@ export async function reconcileDefaultProfileBundle({
460
305
  return { ok: true, status: "EMPTY", changed: false, actions: [] };
461
306
  if (!validateProjection(activeProjection))
462
307
  return refused("default_profile_projection_rejected");
463
- if (
464
- !isAbsolute(dataRoot ?? "") ||
465
- !isAbsolute(profileRoot ?? "") ||
466
- typeof withProfileLock !== "function" ||
467
- typeof reattestAuthority !== "function"
468
- ) {
308
+ if (!isAbsolute(dataRoot ?? "") || !isAbsolute(profileRoot ?? "") ||
309
+ typeof withProfileLock !== "function" || typeof reattestAuthority !== "function") {
469
310
  return refused("default_profile_reconcile_input_rejected");
470
311
  }
471
312
 
@@ -476,62 +317,48 @@ export async function reconcileDefaultProfileBundle({
476
317
  activeProjection.profileId,
477
318
  async () => {
478
319
  const authority = await reattestAuthority(activeProjection);
479
- if (
480
- authority?.operationId !== activeProjection.operationId ||
481
- authority?.authorityGeneration !==
482
- activeProjection.authorityGeneration ||
483
- authority?.fence !== activeProjection.fence
484
- ) {
320
+ if (authority?.operationId !== activeProjection.operationId ||
321
+ authority?.authorityGeneration !== activeProjection.authorityGeneration ||
322
+ authority?.fence !== activeProjection.fence) {
485
323
  return refused("default_profile_authority_stale");
486
324
  }
487
- if (
488
- !existsSync(profileRoot) ||
489
- lstatSync(profileRoot).isSymbolicLink() ||
490
- !lstatSync(profileRoot).isDirectory() ||
491
- realpathSync(profileRoot) !== profileRoot
492
- ) {
325
+ if (!existsSync(profileRoot) || lstatSync(profileRoot).isSymbolicLink() ||
326
+ !lstatSync(profileRoot).isDirectory() || realpathSync(profileRoot) !== profileRoot) {
493
327
  throw new Error("default_profile_root_rejected");
494
328
  }
495
329
 
496
330
  const bundle = readBundle(bundleRoot);
497
331
  const entries = activeEntries(bundle, activeProjection);
498
- if (
499
- materializerReceipt?.profileId !== activeProjection.profileId ||
500
- !verifyDefaultProfileMaterializerReceipt({
501
- profileRoot,
502
- materializerReceipts: bundle.manifest.materializerReceipts,
503
- materializerReceipt,
504
- })
505
- ) {
332
+ if (materializerReceipt?.profileId !== activeProjection.profileId ||
333
+ !materializerReceipt.fileHashes || Array.isArray(materializerReceipt.fileHashes)) {
506
334
  throw new Error("default_profile_materializer_receipt_rejected");
507
335
  }
508
- const materializerReceiptSha256 = sha256(
509
- canonicalJson(materializerReceipt)
510
- );
336
+ for (const path of bundle.manifest.materializerReceipts) {
337
+ if (
338
+ !["SOUL.md", "skills/sellable/SKILL.md", "skills/sellable-admin/SKILL.md"]
339
+ .includes(path) ||
340
+ !SHA256.test(materializerReceipt.fileHashes[path] ?? "") ||
341
+ sha256(readRegular(join(profileRoot, ...path.split("/")), 64 * 1024)) !==
342
+ materializerReceipt.fileHashes[path]
343
+ ) {
344
+ throw new Error("default_profile_materializer_receipt_rejected");
345
+ }
346
+ }
347
+ const materializerReceiptSha256 = sha256(canonicalJson(materializerReceipt));
511
348
 
512
- const receiptPath = join(
513
- profileRoot,
514
- DEFAULT_PROFILE_RECONCILE_RECEIPT
515
- );
349
+ const receiptPath = join(profileRoot, DEFAULT_PROFILE_RECONCILE_RECEIPT);
516
350
  let prior = null;
517
351
  if (existsSync(receiptPath)) {
518
352
  prior = JSON.parse(readRegular(receiptPath).toString("utf8"));
519
- if (
520
- prior?.schemaVersion !== DEFAULT_PROFILE_RECONCILE_SCHEMA ||
521
- prior.profileId !== activeProjection.profileId ||
522
- !Array.isArray(prior.entries)
523
- ) {
353
+ if (prior?.schemaVersion !== DEFAULT_PROFILE_RECONCILE_SCHEMA ||
354
+ prior.profileId !== activeProjection.profileId || !Array.isArray(prior.entries)) {
524
355
  throw new Error("default_profile_receipt_rejected");
525
356
  }
526
357
  }
527
358
  const historical = (entry) =>
528
- prior?.entries.find(
529
- (value) =>
530
- value?.type === entry.type &&
531
- value.id === entry.id &&
532
- (entry.type !== "cron" || value.name === entry.name) &&
533
- SHA256.test(value.sha256 ?? "")
534
- ) ?? null;
359
+ prior?.entries.find((value) => value?.type === entry.type && value.id === entry.id &&
360
+ (entry.type !== "cron" || value.name === entry.name) &&
361
+ SHA256.test(value.sha256 ?? "")) ?? null;
535
362
 
536
363
  // Preflight every skill and cron spec before the first mutation.
537
364
  const skillPlans = new Map();
@@ -541,12 +368,7 @@ export async function reconcileDefaultProfileBundle({
541
368
  cronSpecs.set(entry.id, cronDesired(entry));
542
369
  continue;
543
370
  }
544
- const directory = join(
545
- profileRoot,
546
- "skills",
547
- "sellable-defaults",
548
- entry.id
549
- );
371
+ const directory = join(profileRoot, "skills", "sellable-defaults", entry.id);
550
372
  const path = join(directory, "SKILL.md");
551
373
  if (existsSync(directory)) {
552
374
  const stat = lstatSync(directory);
@@ -556,12 +378,8 @@ export async function reconcileDefaultProfileBundle({
556
378
  if (name === "SKILL.md") continue;
557
379
  const stage = join(directory, name);
558
380
  const stageStat = lstatSync(stage);
559
- if (
560
- !name.startsWith(STAGE_PREFIX) ||
561
- stageStat.isSymbolicLink() ||
562
- !stageStat.isFile() ||
563
- stageStat.nlink !== 1
564
- ) {
381
+ if (!name.startsWith(STAGE_PREFIX) || stageStat.isSymbolicLink() ||
382
+ !stageStat.isFile() || stageStat.nlink !== 1) {
565
383
  throw new Error("default_profile_skill_collision");
566
384
  }
567
385
  rmSync(stage);
@@ -589,51 +407,31 @@ export async function reconcileDefaultProfileBundle({
589
407
  throw new Error("default_profile_skill_readback_rejected");
590
408
  }
591
409
  actions.push({ type: "skill", id: entry.id, status: plan.status });
592
- receiptEntries.push({
593
- type: "skill",
594
- id: entry.id,
595
- sha256: entry.sha256,
596
- });
410
+ receiptEntries.push({ type: "skill", id: entry.id, sha256: entry.sha256 });
597
411
  continue;
598
412
  }
599
413
 
600
- if (
601
- typeof cronAdapter?.create !== "function" ||
602
- typeof cronAdapter?.update !== "function"
603
- ) {
414
+ if (typeof cronAdapter?.create !== "function" ||
415
+ typeof cronAdapter?.update !== "function") {
604
416
  throw new Error("default_profile_cron_adapter_rejected");
605
417
  }
606
- const {
607
- spec,
608
- desired,
609
- sha256: desiredSha256,
610
- } = cronSpecs.get(entry.id);
418
+ const { spec, desired, sha256: desiredSha256 } = cronSpecs.get(entry.id);
611
419
  const cron = await ensureHermesNamedCron({
612
420
  profileRoot,
613
421
  name: entry.name,
614
- isExpected: (job) =>
615
- Boolean(
616
- job?.id &&
617
- job.name === desired.name &&
618
- job.enabled === desired.enabled &&
619
- job.no_agent === desired.no_agent &&
620
- job.script === desired.script &&
621
- job.deliver === desired.deliver &&
622
- job.schedule?.kind === "cron" &&
623
- job.schedule.expr === desired.schedule.expr
624
- ),
625
- isUpdatable: (job) =>
626
- historical(entry)?.sha256 ===
627
- sha256(
628
- canonicalJson({
629
- name: job?.name,
630
- enabled: job?.enabled,
631
- no_agent: job?.no_agent,
632
- schedule: job?.schedule,
633
- script: job?.script,
634
- deliver: job?.deliver,
635
- })
636
- ),
422
+ isExpected: (job) => Boolean(job?.id && job.name === desired.name &&
423
+ job.enabled === desired.enabled && job.no_agent === desired.no_agent &&
424
+ job.script === desired.script && job.deliver === desired.deliver &&
425
+ job.schedule?.kind === "cron" &&
426
+ job.schedule.expr === desired.schedule.expr),
427
+ isUpdatable: (job) => historical(entry)?.sha256 === sha256(canonicalJson({
428
+ name: job?.name,
429
+ enabled: job?.enabled,
430
+ no_agent: job?.no_agent,
431
+ schedule: job?.schedule,
432
+ script: job?.script,
433
+ deliver: job?.deliver,
434
+ })),
637
435
  create: () =>
638
436
  cronAdapter.create({
639
437
  profileRoot,
@@ -654,17 +452,16 @@ export async function reconcileDefaultProfileBundle({
654
452
  codePrefix: "default_profile_cron",
655
453
  });
656
454
  if (!cron.ok) throw new Error(cron.code);
657
- const bound =
658
- typeof cronAdapter.bind === "function"
659
- ? await cronAdapter.bind({
660
- profileRoot,
661
- entry,
662
- spec,
663
- desired,
664
- job: cron.job,
665
- activeProjection,
666
- })
667
- : { changed: false };
455
+ const bound = typeof cronAdapter.bind === "function"
456
+ ? await cronAdapter.bind({
457
+ profileRoot,
458
+ entry,
459
+ spec,
460
+ desired,
461
+ job: cron.job,
462
+ activeProjection,
463
+ })
464
+ : { changed: false };
668
465
  if (!bound || typeof bound.changed !== "boolean")
669
466
  throw new Error("default_profile_cron_binding_rejected");
670
467
  actions.push({
@@ -674,12 +471,7 @@ export async function reconcileDefaultProfileBundle({
674
471
  status: cron.status,
675
472
  bindingChanged: bound.changed,
676
473
  });
677
- receiptEntries.push({
678
- type: "cron",
679
- id: entry.id,
680
- name: entry.name,
681
- sha256: desiredSha256,
682
- });
474
+ receiptEntries.push({ type: "cron", id: entry.id, name: entry.name, sha256: desiredSha256 });
683
475
  }
684
476
 
685
477
  const receipt = `${canonicalJson({
@@ -691,13 +483,10 @@ export async function reconcileDefaultProfileBundle({
691
483
  entries: receiptEntries,
692
484
  })}\n`;
693
485
  const currentReceipt = existsSync(receiptPath)
694
- ? readRegular(receiptPath).toString("utf8")
695
- : null;
696
- if (currentReceipt !== receipt)
697
- atomicWrite(receiptPath, receipt, 0o600);
486
+ ? readRegular(receiptPath).toString("utf8") : null;
487
+ if (currentReceipt !== receipt) atomicWrite(receiptPath, receipt, 0o600);
698
488
  const changed = actions.some(
699
- (action) =>
700
- action.status !== "REUSED" || action.bindingChanged === true
489
+ (action) => action.status !== "REUSED" || action.bindingChanged === true
701
490
  );
702
491
  return {
703
492
  ok: true,
@@ -712,11 +501,8 @@ export async function reconcileDefaultProfileBundle({
712
501
  { dataRoot }
713
502
  );
714
503
  } catch (error) {
715
- const code =
716
- error instanceof Error &&
717
- /^default_profile_[a-z0-9_]+$/.test(error.message)
718
- ? error.message
719
- : "default_profile_reconcile_failed";
504
+ const code = error instanceof Error && /^default_profile_[a-z0-9_]+$/.test(error.message)
505
+ ? error.message : "default_profile_reconcile_failed";
720
506
  return refused(code);
721
507
  }
722
508
  }
@@ -41,7 +41,7 @@ ARG STRIPE_CLI_PACKAGE=@stripe/cli-linux-x64@1.45.0
41
41
  ARG STRIPE_CLI_INTEGRITY=sha512-1ZhoPpoweYfynqsvhCLlTjZ7lPz5IKtxqAMT5MBy6zrwLRTP9x0T6r8h/jLYL8lRH1c7zC/6X6/XUu2V+r9Bog==
42
42
  ARG DEFAULT_PROFILE_BUNDLE_FIXTURE_VERSION=1
43
43
  ARG DEFAULT_PROFILE_BUNDLE_VERSION=1
44
- ARG DEFAULT_PROFILE_BUNDLE_DIGEST=6a224a61458914ce24adeb68d94147244db6d2a3237e7822a819340e09f6202b
44
+ ARG DEFAULT_PROFILE_BUNDLE_DIGEST=e2e306623a85ab6d3fa35409f9324b9cb583a9b8b12f8de1e33bdbdef98fa30f
45
45
 
46
46
  COPY --from=slack-pp-cli-builder --chmod=0555 /out/slack-pp-cli /usr/local/bin/slack-pp-cli
47
47
 
@@ -52,7 +52,6 @@ import {
52
52
  } from "../admin-native-gateway-supervisor.mjs";
53
53
  import {
54
54
  bindDefaultProfileProbeCron,
55
- buildDefaultProfileMaterializerReceipt,
56
55
  buildDefaultProfileProjection,
57
56
  reconcileDefaultProfileBundle,
58
57
  } from "../default-profile-reconciler.mjs";
@@ -264,11 +263,22 @@ function adminDefaultProfileCronAdapter(profileId, profileRoot) {
264
263
  }
265
264
 
266
265
  function adminMaterializerReceipt(profileId, profileRoot) {
267
- return buildDefaultProfileMaterializerReceipt({
266
+ const paths = [
267
+ "SOUL.md",
268
+ "skills/sellable/SKILL.md",
269
+ "skills/sellable-admin/SKILL.md",
270
+ ];
271
+ return {
268
272
  profileId,
269
- profileRoot,
270
- materializerReceipts: ["SOUL.md", "skills/sellable-admin/SKILL.md"],
271
- });
273
+ fileHashes: Object.fromEntries(
274
+ paths
275
+ .filter((relative) => existsSync(join(profileRoot, relative)))
276
+ .map((relative) => [
277
+ relative,
278
+ sha256(readFileSync(join(profileRoot, relative))),
279
+ ])
280
+ ),
281
+ };
272
282
  }
273
283
 
274
284
  function canonicalJson(value) {
@@ -8,7 +8,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
8
8
 
9
9
  ARG MCP_PACKAGE=@sellable/mcp@0.1.880
10
10
  ARG MCP_INTEGRITY=sha512-+Fo8aQlYduLaQMQff+ZA25LeUhWZ7I7UH5DECpND84LQ+rHSjKeKFj4Zs93bVHXwQCIy2OLbr75y4f4+CyT57w==
11
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.656-wip.phase189.20260815.1
11
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.656
12
12
  ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
13
13
  ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
14
14
  ARG TUS_JS_CLIENT_VERSION=4.3.1
@@ -106,7 +106,7 @@ const BOOT_SESSION_ID = randomUUID();
106
106
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
107
107
  const INSTALLER_PACKAGE =
108
108
  process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
109
- "@sellable/install@0.1.656-wip.phase189.20260815.1";
109
+ "@sellable/install@0.1.656";
110
110
  const MCP_PACKAGE =
111
111
  process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.880";
112
112
  const DEFAULT_PROFILE_BUNDLE_ROOT =
@@ -38,7 +38,7 @@ import { installAgentHostServices } from "./service-installer.mjs";
38
38
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
39
39
  const VERSION = "sellable-agent-host-bootstrap/v1";
40
40
  const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
41
- const INSTALLER_PACKAGE = "@sellable/install@0.1.656-wip.phase189.20260815.1";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.656";
42
42
  const MCP_PACKAGE = "@sellable/mcp@0.1.880";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  const SHA256 = /^[a-f0-9]{64}$/;
@@ -1547,7 +1547,7 @@ export function compileClaimToProfileDesired(activeClaim, config) {
1547
1547
  pinned.hermesCli !== "hermes" ||
1548
1548
  pinned.hermesVersion !== "0.18.0" ||
1549
1549
  pinned.installerPackage !==
1550
- "@sellable/install@0.1.656-wip.phase189.20260815.1" ||
1550
+ "@sellable/install@0.1.656" ||
1551
1551
  pinned.mcpPackage !== "@sellable/mcp@0.1.880" ||
1552
1552
  !Array.isArray(policy.toolInclude) ||
1553
1553
  policy.toolInclude.length === 0 ||
@@ -283,7 +283,7 @@ function validateDesired(desired) {
283
283
  desired.hermesCli !== "hermes" ||
284
284
  desired.hermesVersion !== "0.18.0" ||
285
285
  desired.installerPackage !==
286
- "@sellable/install@0.1.656-wip.phase189.20260815.1" ||
286
+ "@sellable/install@0.1.656" ||
287
287
  desired.mcpPackage !== "@sellable/mcp@0.1.880" ||
288
288
  !Array.isArray(desired.toolInclude) ||
289
289
  desired.toolInclude.length === 0 ||
@@ -26,7 +26,7 @@ import { deriveContainedProfileId } from "./profile-materializer.mjs";
26
26
 
27
27
  export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
28
28
  export const PINNED_INSTALL_PACKAGE =
29
- "@sellable/install@0.1.656-wip.phase189.20260815.1";
29
+ "@sellable/install@0.1.656";
30
30
  export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.880";
31
31
  const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
32
32
  PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.656-wip.phase189.20260815.1",
3
+ "version": "0.1.656",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {