@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.
- package/container/Dockerfile +4 -4
- package/container/README.md +2 -2
- package/lib/sellable-agent/admin-runtime-reconciler.mjs +1 -5
- package/lib/sellable-agent/fly-admin-image/Dockerfile +4 -0
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +327 -86
- package/lib/sellable-agent/fly-customer-image/Dockerfile +3 -2
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +233 -100
- package/lib/sellable-agent/fly-customer-image/model-auth-store.mjs +6 -0
- package/lib/sellable-agent/fly-customer-model.mjs +16 -148
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +1 -1
- package/lib/sellable-agent/model-auth-reconciler.mjs +574 -0
- package/lib/sellable-agent/model-health-reporter.mjs +377 -0
- package/lib/sellable-agent/profile-materializer.mjs +17 -2
- package/lib/sellable-agent/provisioning-adapter.mjs +4 -4
- package/package.json +1 -1
|
@@ -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
|
-
|
|
24
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
|
805
|
+
let hermesModelAuthReadback = null;
|
|
799
806
|
try {
|
|
800
|
-
|
|
807
|
+
hermesModelAuthReadback = JSON.parse(hermesModelAuth.stdout);
|
|
801
808
|
} catch {
|
|
802
|
-
|
|
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
|
-
|
|
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${
|
|
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:
|
|
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
|
-
{
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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.
|
|
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}$/;
|