@sellable/install 0.1.483 → 0.1.486-wip.phase169.20260804.2

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.
@@ -16,12 +16,13 @@ import {
16
16
  } from "node:fs";
17
17
  import { dirname, join } from "node:path";
18
18
  import {
19
- FLY_CUSTOMER_MODEL_ID,
20
- FLY_CUSTOMER_MODEL_PROVIDER,
21
- FLY_CUSTOMER_MODEL_REASONING_EFFORT,
22
19
  evaluateModelAuthReadback,
23
- parseFlyCustomerModelCredential,
24
- } from "../fly-customer-model.mjs";
20
+ modelAuthConfigOverlay,
21
+ parseModelAuthRuntimeCredential,
22
+ readModelAuthMaterialization,
23
+ reconcileHermesModelAuth,
24
+ } from "../model-auth-reconciler.mjs";
25
+ import { createModelHealthReporter } from "../model-health-reporter.mjs";
25
26
  import {
26
27
  buildNativeHermesLaunch,
27
28
  clearCustomerRuntimeSecrets,
@@ -37,6 +38,7 @@ import {
37
38
  loadOrCreateFlyRuntimeIdentity,
38
39
  obtainFlyOidcToken,
39
40
  renewFlyCustomerControlSession,
41
+ signedWorkerAuthorization,
40
42
  } from "../fly-runtime-identity.mjs";
41
43
  import {
42
44
  ensureHermesSnapshotCron,
@@ -69,7 +71,7 @@ const RUNTIME_GENERATION = Number(
69
71
  const BOOT_SESSION_ID = randomUUID();
70
72
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
71
73
  const INSTALLER_PACKAGE =
72
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.483";
74
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.486-wip.phase169.20260804.2";
73
75
  const MCP_PACKAGE =
74
76
  process.env.SELLABLE_AGENT_MCP_PACKAGE ??
75
77
  "@sellable/mcp@0.1.805";
@@ -181,6 +183,22 @@ function compileDesired(claim, trustRootPem) {
181
183
  ) {
182
184
  throw new Error("customer_claim_contract_rejected");
183
185
  }
186
+ const modelEnvelope = claim.secretEnvelopes.modelCodex;
187
+ if (
188
+ model.provider !== "codex" ||
189
+ !/^[A-Za-z0-9_.:-]{1,256}$/.test(model.activationId ?? "") ||
190
+ !Number.isSafeInteger(model.bundleGeneration) ||
191
+ model.bundleGeneration < 1 ||
192
+ !/^[a-f0-9]{64}$/.test(model.policyHash ?? "") ||
193
+ !/^[a-f0-9]{16}$/.test(model.credentialFingerprint ?? "") ||
194
+ modelEnvelope.generation !== model.bundleGeneration ||
195
+ modelEnvelope.fingerprint !== model.credentialFingerprint ||
196
+ modelEnvelope.activationId !== model.activationId ||
197
+ modelEnvelope.bundleGeneration !== model.bundleGeneration ||
198
+ modelEnvelope.policyHash !== model.policyHash
199
+ ) {
200
+ throw new Error("customer_model_auth_identity_rejected");
201
+ }
184
202
  const routeGenerations = [
185
203
  ...new Set(selectedBindings.map((binding) => binding.routeGeneration)),
186
204
  ];
@@ -244,7 +262,10 @@ function compileDesired(claim, trustRootPem) {
244
262
  routeGeneration: routeGenerations[0],
245
263
  membershipGeneration: membershipGenerations[0],
246
264
  serviceCredentialGeneration: service.credentialGeneration,
247
- modelCredentialGeneration: model.credentialGeneration,
265
+ modelCredentialGeneration: model.bundleGeneration,
266
+ modelActivationId: model.activationId,
267
+ modelBundleGeneration: model.bundleGeneration,
268
+ modelPolicyHash: model.policyHash,
248
269
  runtimeGeneration: RUNTIME_GENERATION,
249
270
  selectedChannelIds,
250
271
  defaultChannelId: defaultBinding.channelId,
@@ -400,24 +421,13 @@ function writeRuntimeHermesConfig({
400
421
  const runtimeConfigRoot = profileRoot;
401
422
  mkdirSync(runtimeConfigRoot, { recursive: true, mode: 0o700 });
402
423
  chmodSync(runtimeConfigRoot, 0o700);
403
- const priorIdentityPath = join(runtimeConfigRoot, "runtime-identity.json");
404
- let priorModelCredentialGeneration = null;
405
- if (existsSync(priorIdentityPath)) {
406
- try {
407
- const priorIdentity = JSON.parse(readFileSync(priorIdentityPath, "utf8"));
408
- if (
409
- Number.isSafeInteger(priorIdentity.modelCredentialGeneration) &&
410
- priorIdentity.modelCredentialGeneration > 0
411
- ) {
412
- priorModelCredentialGeneration =
413
- priorIdentity.modelCredentialGeneration;
414
- }
415
- } catch {
416
- priorModelCredentialGeneration = null;
417
- }
418
- }
419
424
  const localHermesAuthStorePath = join(runtimeConfigRoot, "auth.json");
420
425
  const hermesRootAuthStorePath = join(DATA_ROOT, "auth.json");
426
+ const hermesRootAuthMarkerPath = join(
427
+ DATA_ROOT,
428
+ ".sellable-agent",
429
+ "model-auth-source.json"
430
+ );
421
431
  if (existsSync(localHermesAuthStorePath)) {
422
432
  const localAuthStore = lstatSync(localHermesAuthStorePath);
423
433
  if (localAuthStore.isSymbolicLink() || !localAuthStore.isFile()) {
@@ -425,17 +435,6 @@ function writeRuntimeHermesConfig({
425
435
  }
426
436
  unlinkSync(localHermesAuthStorePath);
427
437
  }
428
- if (
429
- priorModelCredentialGeneration !== null &&
430
- priorModelCredentialGeneration !== desired.modelCredentialGeneration &&
431
- existsSync(hermesRootAuthStorePath)
432
- ) {
433
- const authStore = lstatSync(hermesRootAuthStorePath);
434
- if (authStore.isSymbolicLink() || !authStore.isFile()) {
435
- throw new Error("customer_model_auth_store_rejected");
436
- }
437
- unlinkSync(hermesRootAuthStorePath);
438
- }
439
438
  const mcpConfig = JSON.parse(
440
439
  readFileSync(join(profileRoot, "sellable", "mcp.json"), "utf8")
441
440
  );
@@ -477,22 +476,25 @@ function writeRuntimeHermesConfig({
477
476
  const testModelOrigin = allowTestProviders()
478
477
  ? process.env.SELLABLE_AGENT_TEST_MODEL_ORIGIN
479
478
  : null;
480
- const modelCredential = parseFlyCustomerModelCredential(secrets.modelCodex);
481
- const modelAuthPath = join(secretRoot, "auth.json");
482
- atomicFile(modelAuthPath, modelCredential.authJson, 0o600);
483
- if (!existsSync(hermesRootAuthStorePath)) {
484
- atomicFile(
485
- hermesRootAuthStorePath,
486
- modelCredential.hermesRootAuthJson,
487
- 0o600
488
- );
489
- } else {
490
- const authStore = lstatSync(hermesRootAuthStorePath);
491
- if (authStore.isSymbolicLink() || !authStore.isFile()) {
492
- throw new Error("customer_model_auth_store_rejected");
493
- }
494
- chmodSync(hermesRootAuthStorePath, 0o600);
479
+ const modelCredential = parseModelAuthRuntimeCredential(secrets.modelCodex);
480
+ if (
481
+ modelCredential.activationId !== desired.modelActivationId ||
482
+ modelCredential.bundleGeneration !== desired.modelBundleGeneration ||
483
+ modelCredential.policyHash !== desired.modelPolicyHash
484
+ ) {
485
+ throw new Error("customer_model_auth_identity_rejected");
495
486
  }
487
+ const modelBundlePath = join(secretRoot, "model-auth-runtime-v2.json");
488
+ atomicFile(modelBundlePath, modelCredential.authJson, 0o600);
489
+ const modelLifecycle =
490
+ claim.operation.action === "INSTANTIATE" ? "OFF" : "ON";
491
+ const modelAuthState = reconcileHermesModelAuth({
492
+ authStorePath: hermesRootAuthStorePath,
493
+ markerPath: hermesRootAuthMarkerPath,
494
+ credential: modelCredential,
495
+ lifecycle: modelLifecycle,
496
+ });
497
+ const modelConfig = modelAuthConfigOverlay(modelCredential);
496
498
  atomicJson(runtimeConfigPath, {
497
499
  ...baseConfig,
498
500
  ...(testModelOrigin
@@ -515,12 +517,7 @@ function writeRuntimeHermesConfig({
515
517
  ],
516
518
  }
517
519
  : {
518
- model: {
519
- default: FLY_CUSTOMER_MODEL_ID,
520
- provider: FLY_CUSTOMER_MODEL_PROVIDER,
521
- reasoning_effort: FLY_CUSTOMER_MODEL_REASONING_EFFORT,
522
- generation: desired.modelCredentialGeneration,
523
- },
520
+ ...modelConfig,
524
521
  }),
525
522
  mcp_servers: {
526
523
  ...baseConfig.mcp_servers,
@@ -548,10 +545,10 @@ function writeRuntimeHermesConfig({
548
545
  });
549
546
  const gatesPath = join(secretRoot, "gates.json");
550
547
  atomicJson(gatesPath, {
551
- lifecycle: "ON",
552
- dispatch: "ENABLED",
553
- route: "OPEN",
554
- output: "OPEN",
548
+ lifecycle: modelLifecycle,
549
+ dispatch: modelLifecycle === "ON" ? "ENABLED" : "DISABLED",
550
+ route: modelLifecycle === "ON" ? "OPEN" : "CLOSED",
551
+ output: modelLifecycle === "ON" ? "OPEN" : "CLOSED",
555
552
  });
556
553
  atomicJson(join(runtimeConfigRoot, "runtime-identity.json"), {
557
554
  runtimeId: RUNTIME_ID,
@@ -564,14 +561,20 @@ function writeRuntimeHermesConfig({
564
561
  membershipGeneration: desired.membershipGeneration,
565
562
  serviceCredentialGeneration: desired.serviceCredentialGeneration,
566
563
  modelCredentialGeneration: desired.modelCredentialGeneration,
564
+ modelActivationId: desired.modelActivationId,
565
+ modelBundleGeneration: desired.modelBundleGeneration,
566
+ modelPolicyHash: desired.modelPolicyHash,
567
567
  });
568
568
  return {
569
569
  runtimeHome: runtimeConfigRoot,
570
570
  runtimeConfigPath,
571
571
  runtimeMcpPath,
572
572
  gatesPath,
573
- modelAuthPath,
573
+ modelBundlePath,
574
574
  hermesRootAuthStorePath,
575
+ hermesRootAuthMarkerPath,
576
+ modelAuthSourceFingerprint: modelAuthState.sourceFingerprint,
577
+ modelAuthState,
575
578
  modelCredential,
576
579
  };
577
580
  }
@@ -611,7 +614,7 @@ function applicationRuntimeEnv({ desired, runtimeConfig, secrets }) {
611
614
  HERMES_HOME: DATA_ROOT,
612
615
  SELLABLE_HERMES_PROFILE_ID: desired.profileId,
613
616
  HERMES_CONFIG_PATH: runtimeConfig.runtimeConfigPath,
614
- CODEX_HOME: dirname(runtimeConfig.modelAuthPath),
617
+ CODEX_HOME: dirname(runtimeConfig.modelBundlePath),
615
618
  SELLABLE_AGENT_RUNTIME: "1",
616
619
  SELLABLE_AGENT_RUNTIME_GATE_FILE: runtimeConfig.gatesPath,
617
620
  SLACK_HOME_CHANNEL: desired.defaultChannelId,
@@ -771,54 +774,100 @@ async function proveProvisionedApplication({
771
774
  profileDigest,
772
775
  }) {
773
776
  const runtimeEnv = applicationRuntimeEnv({ desired, runtimeConfig, secrets });
774
- const [hermesVersion, mcpVersion, modelAuth] = await Promise.all([
777
+ const materializedModelAuth = runtimeConfig.modelAuthState.materialized;
778
+ const [hermesVersion, mcpVersion, hermesModelAuth] = await Promise.all([
775
779
  run(HERMES, ["--version"], { env: runtimeEnv, timeoutMs: 180_000 }),
776
780
  run(MCP, ["--version"], { env: runtimeEnv, timeoutMs: 180_000 }),
777
- run(
778
- HERMES_PYTHON,
779
- [
780
- "-c",
781
- [
782
- "import json",
783
- "from pathlib import Path",
784
- "from hermes_constants import get_default_hermes_root, get_hermes_home",
785
- "from hermes_cli.auth import resolve_codex_runtime_credentials",
786
- "value = resolve_codex_runtime_credentials(refresh_if_expiring=False)",
787
- 'root_auth = get_default_hermes_root() / "auth.json"',
788
- 'local_auth = get_hermes_home() / "auth.json"',
789
- 'print(json.dumps({"provider": value.get("provider"), "source": value.get("source"), "source_class": "root_fallback" if root_auth.is_file() and not local_auth.exists() else "unexpected", "auth_mode": value.get("auth_mode"), "credential_present": bool(value.get("api_key")), "local_auth_entry_count": 0 if not local_auth.exists() else 1}))',
790
- ].join(";"),
791
- ],
792
- {
793
- env: { ...runtimeEnv, HERMES_HOME: runtimeConfig.runtimeHome },
794
- timeoutMs: 180_000,
795
- }
796
- ),
781
+ materializedModelAuth
782
+ ? run(
783
+ HERMES_PYTHON,
784
+ [
785
+ "-c",
786
+ [
787
+ "import json",
788
+ "from agent.credential_pool import load_pool",
789
+ 'pool = load_pool("openai-codex")',
790
+ "entry = pool.select() if pool else None",
791
+ 'print(json.dumps({"provider": "openai-codex", "source": "credential_pool", "credential_present": bool(entry and entry.runtime_api_key), "credential_id": getattr(entry, "id", None), "runtime_refresh_owned": bool(getattr(entry, "refresh_token", None))}))',
792
+ ].join(";"),
793
+ ],
794
+ {
795
+ env: { ...runtimeEnv, HERMES_HOME: runtimeConfig.runtimeHome },
796
+ timeoutMs: 180_000,
797
+ }
798
+ )
799
+ : Promise.resolve({
800
+ status: 0,
801
+ stdout: JSON.stringify({ materialization_pending_start: true }),
802
+ stderr: "",
803
+ }),
797
804
  ]);
798
- let modelAuthReadback = null;
805
+ let hermesModelAuthReadback = null;
799
806
  try {
800
- modelAuthReadback = JSON.parse(modelAuth.stdout);
807
+ hermesModelAuthReadback = JSON.parse(hermesModelAuth.stdout);
801
808
  } catch {
802
- modelAuthReadback = null;
809
+ hermesModelAuthReadback = null;
810
+ }
811
+ const modelAuthReadback = materializedModelAuth
812
+ ? readModelAuthMaterialization({
813
+ authStorePath: runtimeConfig.hermesRootAuthStorePath,
814
+ markerPath: runtimeConfig.hermesRootAuthMarkerPath,
815
+ })
816
+ : {
817
+ schemaVersion: "sellable-agent-model-auth-readback/v2",
818
+ activationId: desired.modelActivationId,
819
+ bundleGeneration: desired.modelBundleGeneration,
820
+ policyHash: desired.modelPolicyHash,
821
+ provider: "openai-codex",
822
+ model: runtimeConfig.modelCredential.model,
823
+ fallbackDepth:
824
+ runtimeConfig.modelCredential.value.openrouter.models.length,
825
+ desiredStatus: "converged",
826
+ materializedStatus: "pending_start",
827
+ slots: runtimeConfig.modelCredential.identity.slots.map((slot) => ({
828
+ slotId: slot.slotId,
829
+ grantFingerprint: slot.grantFingerprint,
830
+ expiresAt: new Date(slot.expiresAt).toISOString(),
831
+ state: "ok",
832
+ lastErrorReason: null,
833
+ lastErrorResetAt: null,
834
+ })),
835
+ openrouter: {
836
+ keyFingerprint:
837
+ runtimeConfig.modelCredential.identity.openrouterKeyFingerprint,
838
+ state: "ok",
839
+ },
840
+ };
841
+ const modelAuthVerdict = materializedModelAuth
842
+ ? evaluateModelAuthReadback(modelAuthReadback, {
843
+ activationId: desired.modelActivationId,
844
+ bundleGeneration: desired.modelBundleGeneration,
845
+ policyHash: desired.modelPolicyHash,
846
+ })
847
+ : { accepted: true, rejection: null };
848
+ if (
849
+ materializedModelAuth &&
850
+ (hermesModelAuth.status !== 0 ||
851
+ hermesModelAuthReadback?.provider !== "openai-codex" ||
852
+ hermesModelAuthReadback?.source !== "credential_pool" ||
853
+ hermesModelAuthReadback?.credential_present !== true ||
854
+ hermesModelAuthReadback?.runtime_refresh_owned !== false ||
855
+ !["dittto-primary", "kaigo-secondary"].includes(
856
+ hermesModelAuthReadback?.credential_id
857
+ ))
858
+ ) {
859
+ throw new Error("access_only_snapshot_infeasible");
803
860
  }
804
- const modelAuthVerdict = evaluateModelAuthReadback(modelAuthReadback);
805
- if (modelAuth.status !== 0 || !modelAuthVerdict.accepted) {
806
- // Name the failed invariant: a bare rejection sent the 2026-07-28 outage
807
- // hunting the credential itself when the credential was valid and only its
808
- // storage shape in the root auth store had changed.
861
+ if (!modelAuthVerdict.accepted) {
809
862
  throw new Error(
810
- `customer_model_auth_readback_rejected:${
811
- modelAuth.status !== 0
812
- ? "probe_failed"
813
- : (modelAuthVerdict.rejection ?? "unknown")
814
- }`
863
+ `customer_model_auth_readback_rejected:${modelAuthVerdict.rejection ?? "unknown"}`
815
864
  );
816
865
  }
817
866
  if (
818
867
  hermesVersion.status !== 0 ||
819
868
  mcpVersion.status !== 0 ||
820
869
  /Lazy-installing|pip install|npm install/i.test(
821
- `${hermesVersion.stdout}\n${hermesVersion.stderr}\n${mcpVersion.stdout}\n${mcpVersion.stderr}\n${modelAuth.stdout}\n${modelAuth.stderr}`
870
+ `${hermesVersion.stdout}\n${hermesVersion.stderr}\n${mcpVersion.stdout}\n${mcpVersion.stderr}\n${hermesModelAuth.stdout}\n${hermesModelAuth.stderr}`
822
871
  )
823
872
  ) {
824
873
  throw new Error("customer_runtime_binary_rejected");
@@ -827,7 +876,10 @@ async function proveProvisionedApplication({
827
876
  profileDigest,
828
877
  hermesOutput: hermesVersion.stdout,
829
878
  mcpOutput: mcpVersion.stdout,
830
- modelAuthOutput: modelAuth.stdout,
879
+ modelAuthOutput: `${hermesModelAuth.stdout}\n${JSON.stringify(
880
+ modelAuthReadback
881
+ )}`,
882
+ modelAuthReadback,
831
883
  };
832
884
  }
833
885
 
@@ -956,7 +1008,13 @@ function completionReceipt({
956
1008
  { code: "NATIVE_SLACK_GATEWAY", status: "TRUE" },
957
1009
  ]),
958
1010
  { code: "MCP_PREBAKED", status: "TRUE" },
959
- { code: "CODEX_OAUTH_READBACK", status: "TRUE" },
1011
+ {
1012
+ code:
1013
+ claim.operation.action === "INSTANTIATE"
1014
+ ? "MODEL_AUTH_DESIRED_PENDING_START"
1015
+ : "CODEX_OAUTH_READBACK",
1016
+ status: "TRUE",
1017
+ },
960
1018
  { code: "NO_BOOT_INSTALL", status: "TRUE" },
961
1019
  { code: "DEDICATED_SLACK_APP", status: "TRUE" },
962
1020
  ],
@@ -999,7 +1057,52 @@ async function flushHermesSnapshot({ client, profileId, profileRoot }) {
999
1057
  }
1000
1058
  }
1001
1059
 
1002
- async function processOperation({ claim, exchange, client }) {
1060
+ function runtimeModelHealthReporter({ identity, exchange, runtimeConfig }) {
1061
+ return createModelHealthReporter({
1062
+ origin: ORIGIN,
1063
+ identity: {
1064
+ kind: "CUSTOMER",
1065
+ runtimeGeneration: RUNTIME_GENERATION,
1066
+ workloadKeyHash: exchange.publicKeyHash,
1067
+ machineId: exchange.workerId,
1068
+ adminRuntimeGenerationId: null,
1069
+ adminRuntimeGenerationFence: null,
1070
+ },
1071
+ observe: () =>
1072
+ readModelAuthMaterialization({
1073
+ authStorePath: runtimeConfig.hermesRootAuthStorePath,
1074
+ markerPath: runtimeConfig.hermesRootAuthMarkerPath,
1075
+ }),
1076
+ authorize: (body) =>
1077
+ signedWorkerAuthorization({
1078
+ identity,
1079
+ exchange,
1080
+ verb: "model-health",
1081
+ body,
1082
+ now: new Date(),
1083
+ unbound: true,
1084
+ }),
1085
+ onError: (error) => {
1086
+ process.stderr.write(
1087
+ `${JSON.stringify({
1088
+ ok: false,
1089
+ code:
1090
+ error instanceof Error
1091
+ ? error.message
1092
+ : "model_health_report_failed",
1093
+ })}\n`
1094
+ );
1095
+ },
1096
+ allowHttpLoopback: allowTestProviders(),
1097
+ sequencePath: join(
1098
+ DATA_ROOT,
1099
+ ".sellable-agent",
1100
+ "model-health-sequence.json"
1101
+ ),
1102
+ });
1103
+ }
1104
+
1105
+ async function processOperation({ claim, exchange, client, identity }) {
1003
1106
  const trustRootPem = readFileSync(TRUST_ROOT, "utf8");
1004
1107
  const { desired, toolInclude } = compileDesired(claim, trustRootPem);
1005
1108
  mkdirSync(PROFILES_ROOT, { recursive: true, mode: 0o700 });
@@ -1063,6 +1166,27 @@ async function processOperation({ claim, exchange, client }) {
1063
1166
  const completed = await client.complete(receipt);
1064
1167
  if (completed?.accepted !== true)
1065
1168
  throw new Error("customer_completion_rejected");
1169
+ let modelHealthReporter = null;
1170
+ if (claim.operation.action !== "INSTANTIATE") {
1171
+ modelHealthReporter = runtimeModelHealthReporter({
1172
+ identity,
1173
+ exchange,
1174
+ runtimeConfig,
1175
+ });
1176
+ try {
1177
+ await modelHealthReporter.start("materialized");
1178
+ } catch (error) {
1179
+ process.stderr.write(
1180
+ `${JSON.stringify({
1181
+ ok: false,
1182
+ code:
1183
+ error instanceof Error
1184
+ ? error.message
1185
+ : "model_health_report_failed",
1186
+ })}\n`
1187
+ );
1188
+ }
1189
+ }
1066
1190
  atomicJson(join(DATA_ROOT, "sellable-agent-runtime-proof.json"), {
1067
1191
  ok: true,
1068
1192
  runtimeId: RUNTIME_ID,
@@ -1073,6 +1197,7 @@ async function processOperation({ claim, exchange, client }) {
1073
1197
  revisionId: desired.revisionId,
1074
1198
  observedLifecycle: receipt.observedLifecycle,
1075
1199
  profileDigest: proof.profileDigest,
1200
+ modelAuth: proof.modelAuthReadback,
1076
1201
  runtimeUid: process.getuid?.() ?? null,
1077
1202
  runtimeGid: process.getgid?.() ?? null,
1078
1203
  profileMode: (lstatSync(profileRoot).mode & 0o777)
@@ -1099,6 +1224,7 @@ async function processOperation({ claim, exchange, client }) {
1099
1224
  trustRootPem,
1100
1225
  action: claim.operation.action,
1101
1226
  gateway: proof.gateway ?? null,
1227
+ modelHealthReporter,
1102
1228
  };
1103
1229
  }
1104
1230
 
@@ -1241,8 +1367,14 @@ async function main() {
1241
1367
  exchange,
1242
1368
  client,
1243
1369
  });
1370
+ await activeRuntime?.modelHealthReporter?.stop();
1244
1371
  await stopNativeGateway(activeRuntime?.gateway);
1245
- activeRuntime = await processOperation({ claim, exchange, client });
1372
+ activeRuntime = await processOperation({
1373
+ claim,
1374
+ exchange,
1375
+ client,
1376
+ identity,
1377
+ });
1246
1378
  operationHeartbeat.assertHealthy();
1247
1379
  await operationHeartbeat.stop();
1248
1380
  operationHeartbeat = null;
@@ -1277,6 +1409,7 @@ async function main() {
1277
1409
  );
1278
1410
  }
1279
1411
  await stopNativeGateway(activeRuntime?.gateway);
1412
+ await activeRuntime?.modelHealthReporter?.stop();
1280
1413
  if (activeRuntime?.desired.profileId) {
1281
1414
  clearCustomerRuntimeSecrets({
1282
1415
  secretsRoot: SECRET_ROOT,
@@ -0,0 +1,6 @@
1
+ // Compatibility-only export. Runtime behavior lives in the neutral module
2
+ // copied byte-for-byte into both CUSTOMER and ADMIN image contexts.
3
+ export {
4
+ reconcileHermesModelAuth,
5
+ reconcileHermesModelAuth as reconcileHermesRootModelAuth,
6
+ } from "../model-auth-reconciler.mjs";
@@ -1,152 +1,20 @@
1
- export const FLY_CUSTOMER_MODEL_PROVIDER = "openai-codex";
2
- export const FLY_CUSTOMER_MODEL_ID = "gpt-5.6-sol";
3
- export const FLY_CUSTOMER_MODEL_REASONING_EFFORT = "high";
1
+ import {
2
+ MODEL_AUTH_DEFAULT_MODEL,
3
+ MODEL_AUTH_PROVIDER,
4
+ MODEL_AUTH_REASONING_EFFORT,
5
+ evaluateModelAuthReadback,
6
+ parseModelAuthRuntimeCredential,
7
+ } from "./model-auth-reconciler.mjs";
4
8
 
5
- const MODEL_CREDENTIAL_KEYS = [
6
- "OPENAI_API_KEY",
7
- "auth_mode",
8
- "last_refresh",
9
- "tokens",
10
- ];
11
- const MODEL_TOKEN_KEYS = [
12
- "access_token",
13
- "account_id",
14
- "id_token",
15
- "refresh_token",
16
- ];
17
- /**
18
- * Credential sources that count as "resolved from this runtime's own root auth
19
- * store". `hermes-auth-store` is the seeded singleton under `providers`;
20
- * `credential_pool` is the pooled entry — which this module seeds itself (see
21
- * `hermesRootAuthJson` below) and which `hermes auth add` also writes when an
22
- * operator re-authenticates an expired Codex token. Both live in the same root
23
- * auth.json, so neither weakens isolation: that guarantee is carried by
24
- * `source_class === "root_fallback"` and `local_auth_entry_count === 0`, which
25
- * together prove no profile-local auth.json is shadowing the root store.
26
- */
27
- const TRUSTED_MODEL_AUTH_SOURCES = ["hermes-auth-store", "credential_pool"];
9
+ export const FLY_CUSTOMER_MODEL_PROVIDER = MODEL_AUTH_PROVIDER;
10
+ export const FLY_CUSTOMER_MODEL_ID = MODEL_AUTH_DEFAULT_MODEL;
11
+ export const FLY_CUSTOMER_MODEL_REASONING_EFFORT =
12
+ MODEL_AUTH_REASONING_EFFORT;
28
13
 
29
- /**
30
- * Decide whether the runtime's model-auth readback proves a usable, isolated
31
- * Codex credential. Returns the first failed invariant so boot failures name
32
- * the actual cause instead of a generic rejection.
33
- */
34
- export function evaluateModelAuthReadback(readback) {
35
- const reject = (rejection) => ({ accepted: false, rejection });
36
- if (!readback || typeof readback !== "object" || Array.isArray(readback)) {
37
- return reject("readback_unparsable");
38
- }
39
- if (readback.provider !== FLY_CUSTOMER_MODEL_PROVIDER) {
40
- return reject("provider_mismatch");
41
- }
42
- if (readback.source_class !== "root_fallback") {
43
- return reject("source_class_not_root_fallback");
44
- }
45
- if (readback.local_auth_entry_count !== 0) {
46
- return reject("local_auth_entry_present");
47
- }
48
- if (!TRUSTED_MODEL_AUTH_SOURCES.includes(readback.source)) {
49
- return reject("source_not_trusted");
50
- }
51
- if (readback.auth_mode !== "chatgpt") {
52
- return reject("auth_mode_not_chatgpt");
53
- }
54
- if (readback.credential_present !== true) {
55
- return reject("credential_absent");
56
- }
57
- return { accepted: true, rejection: null };
14
+ // Kept as the package's stable import name while the on-wire credential is
15
+ // upgraded from a singleton full grant to the v2 access-only runtime bundle.
16
+ export function parseFlyCustomerModelCredential(secret, options) {
17
+ return parseModelAuthRuntimeCredential(secret, options);
58
18
  }
59
19
 
60
- const exactKeys = (value, expected) =>
61
- value &&
62
- typeof value === "object" &&
63
- !Array.isArray(value) &&
64
- Object.keys(value).length === expected.length &&
65
- Object.keys(value).every((key) => expected.includes(key));
66
- const bounded = (value, minimum, maximum) =>
67
- typeof value === "string" &&
68
- value.length >= minimum &&
69
- value.length <= maximum &&
70
- !value.includes("\0");
71
-
72
- export function parseFlyCustomerModelCredential(secret) {
73
- if (
74
- typeof secret !== "string" ||
75
- !secret ||
76
- Buffer.byteLength(secret, "utf8") > 64 * 1024
77
- ) {
78
- throw new Error("customer_model_credential_rejected");
79
- }
80
- let value;
81
- try {
82
- value = JSON.parse(secret);
83
- } catch {
84
- throw new Error("customer_model_credential_rejected");
85
- }
86
- if (
87
- !exactKeys(value, MODEL_CREDENTIAL_KEYS) ||
88
- value.auth_mode !== "chatgpt" ||
89
- value.OPENAI_API_KEY !== null ||
90
- !bounded(value.last_refresh, 10, 128) ||
91
- Number.isNaN(Date.parse(value.last_refresh)) ||
92
- !exactKeys(value.tokens, MODEL_TOKEN_KEYS) ||
93
- !bounded(value.tokens.id_token, 32, 32 * 1024) ||
94
- !bounded(value.tokens.access_token, 32, 32 * 1024) ||
95
- !bounded(value.tokens.refresh_token, 32, 16 * 1024) ||
96
- !bounded(value.tokens.account_id, 1, 512)
97
- ) {
98
- throw new Error("customer_model_credential_rejected");
99
- }
100
- const authJson = JSON.stringify({
101
- auth_mode: "chatgpt",
102
- OPENAI_API_KEY: null,
103
- tokens: {
104
- id_token: value.tokens.id_token,
105
- access_token: value.tokens.access_token,
106
- refresh_token: value.tokens.refresh_token,
107
- account_id: value.tokens.account_id,
108
- },
109
- last_refresh: value.last_refresh,
110
- });
111
- const hermesRootAuthJson = JSON.stringify({
112
- version: 1,
113
- providers: {
114
- "openai-codex": {
115
- tokens: {
116
- id_token: value.tokens.id_token,
117
- access_token: value.tokens.access_token,
118
- refresh_token: value.tokens.refresh_token,
119
- account_id: value.tokens.account_id,
120
- },
121
- last_refresh: value.last_refresh,
122
- auth_mode: "chatgpt",
123
- },
124
- },
125
- credential_pool: {
126
- "openai-codex": [
127
- {
128
- id: "sellable-control-plane",
129
- label: "Sellable Codex OAuth",
130
- auth_type: "oauth",
131
- source: "device_code",
132
- access_token: value.tokens.access_token,
133
- refresh_token: value.tokens.refresh_token,
134
- id_token: value.tokens.id_token,
135
- account_id: value.tokens.account_id,
136
- last_refresh: value.last_refresh,
137
- priority: 0,
138
- request_count: 0,
139
- },
140
- ],
141
- },
142
- active_provider: "openai-codex",
143
- });
144
- return Object.freeze({
145
- provider: FLY_CUSTOMER_MODEL_PROVIDER,
146
- model: FLY_CUSTOMER_MODEL_ID,
147
- reasoningEffort: FLY_CUSTOMER_MODEL_REASONING_EFFORT,
148
- authJson,
149
- hermesRootAuthJson,
150
- testAccessToken: value.tokens.access_token,
151
- });
152
- }
20
+ export { evaluateModelAuthReadback };
@@ -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.483";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.486-wip.phase169.20260804.2";
42
42
  const MCP_PACKAGE = "@sellable/mcp@0.1.760-wip.phase143.20260727100513";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  const SHA256 = /^[a-f0-9]{64}$/;