@sellable/install 0.1.484 → 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 +2 -2
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +224 -70
- package/lib/sellable-agent/fly-customer-image/model-auth-store.mjs +6 -143
- package/lib/sellable-agent/fly-customer-model.mjs +16 -157
- 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,
|
|
@@ -48,7 +50,6 @@ import {
|
|
|
48
50
|
materializeNativeSlackCustomerProfile,
|
|
49
51
|
validateNativeSlackCustomerProfile,
|
|
50
52
|
} from "../profile-materializer.mjs";
|
|
51
|
-
import { reconcileHermesRootModelAuth } from "./model-auth-store.mjs";
|
|
52
53
|
|
|
53
54
|
const DATA_ROOT = "/opt/data";
|
|
54
55
|
const PROFILES_ROOT = join(DATA_ROOT, "profiles");
|
|
@@ -70,7 +71,7 @@ const RUNTIME_GENERATION = Number(
|
|
|
70
71
|
const BOOT_SESSION_ID = randomUUID();
|
|
71
72
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
|
|
72
73
|
const INSTALLER_PACKAGE =
|
|
73
|
-
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";
|
|
74
75
|
const MCP_PACKAGE =
|
|
75
76
|
process.env.SELLABLE_AGENT_MCP_PACKAGE ??
|
|
76
77
|
"@sellable/mcp@0.1.805";
|
|
@@ -182,6 +183,22 @@ function compileDesired(claim, trustRootPem) {
|
|
|
182
183
|
) {
|
|
183
184
|
throw new Error("customer_claim_contract_rejected");
|
|
184
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
|
+
}
|
|
185
202
|
const routeGenerations = [
|
|
186
203
|
...new Set(selectedBindings.map((binding) => binding.routeGeneration)),
|
|
187
204
|
];
|
|
@@ -245,7 +262,10 @@ function compileDesired(claim, trustRootPem) {
|
|
|
245
262
|
routeGeneration: routeGenerations[0],
|
|
246
263
|
membershipGeneration: membershipGenerations[0],
|
|
247
264
|
serviceCredentialGeneration: service.credentialGeneration,
|
|
248
|
-
modelCredentialGeneration: model.
|
|
265
|
+
modelCredentialGeneration: model.bundleGeneration,
|
|
266
|
+
modelActivationId: model.activationId,
|
|
267
|
+
modelBundleGeneration: model.bundleGeneration,
|
|
268
|
+
modelPolicyHash: model.policyHash,
|
|
249
269
|
runtimeGeneration: RUNTIME_GENERATION,
|
|
250
270
|
selectedChannelIds,
|
|
251
271
|
defaultChannelId: defaultBinding.channelId,
|
|
@@ -456,15 +476,25 @@ function writeRuntimeHermesConfig({
|
|
|
456
476
|
const testModelOrigin = allowTestProviders()
|
|
457
477
|
? process.env.SELLABLE_AGENT_TEST_MODEL_ORIGIN
|
|
458
478
|
: null;
|
|
459
|
-
const modelCredential =
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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");
|
|
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({
|
|
463
492
|
authStorePath: hermesRootAuthStorePath,
|
|
464
493
|
markerPath: hermesRootAuthMarkerPath,
|
|
465
|
-
modelCredential,
|
|
466
|
-
|
|
494
|
+
credential: modelCredential,
|
|
495
|
+
lifecycle: modelLifecycle,
|
|
467
496
|
});
|
|
497
|
+
const modelConfig = modelAuthConfigOverlay(modelCredential);
|
|
468
498
|
atomicJson(runtimeConfigPath, {
|
|
469
499
|
...baseConfig,
|
|
470
500
|
...(testModelOrigin
|
|
@@ -487,12 +517,7 @@ function writeRuntimeHermesConfig({
|
|
|
487
517
|
],
|
|
488
518
|
}
|
|
489
519
|
: {
|
|
490
|
-
|
|
491
|
-
default: FLY_CUSTOMER_MODEL_ID,
|
|
492
|
-
provider: FLY_CUSTOMER_MODEL_PROVIDER,
|
|
493
|
-
reasoning_effort: FLY_CUSTOMER_MODEL_REASONING_EFFORT,
|
|
494
|
-
generation: desired.modelCredentialGeneration,
|
|
495
|
-
},
|
|
520
|
+
...modelConfig,
|
|
496
521
|
}),
|
|
497
522
|
mcp_servers: {
|
|
498
523
|
...baseConfig.mcp_servers,
|
|
@@ -520,10 +545,10 @@ function writeRuntimeHermesConfig({
|
|
|
520
545
|
});
|
|
521
546
|
const gatesPath = join(secretRoot, "gates.json");
|
|
522
547
|
atomicJson(gatesPath, {
|
|
523
|
-
lifecycle:
|
|
524
|
-
dispatch: "ENABLED",
|
|
525
|
-
route: "OPEN",
|
|
526
|
-
output: "OPEN",
|
|
548
|
+
lifecycle: modelLifecycle,
|
|
549
|
+
dispatch: modelLifecycle === "ON" ? "ENABLED" : "DISABLED",
|
|
550
|
+
route: modelLifecycle === "ON" ? "OPEN" : "CLOSED",
|
|
551
|
+
output: modelLifecycle === "ON" ? "OPEN" : "CLOSED",
|
|
527
552
|
});
|
|
528
553
|
atomicJson(join(runtimeConfigRoot, "runtime-identity.json"), {
|
|
529
554
|
runtimeId: RUNTIME_ID,
|
|
@@ -536,16 +561,20 @@ function writeRuntimeHermesConfig({
|
|
|
536
561
|
membershipGeneration: desired.membershipGeneration,
|
|
537
562
|
serviceCredentialGeneration: desired.serviceCredentialGeneration,
|
|
538
563
|
modelCredentialGeneration: desired.modelCredentialGeneration,
|
|
564
|
+
modelActivationId: desired.modelActivationId,
|
|
565
|
+
modelBundleGeneration: desired.modelBundleGeneration,
|
|
566
|
+
modelPolicyHash: desired.modelPolicyHash,
|
|
539
567
|
});
|
|
540
568
|
return {
|
|
541
569
|
runtimeHome: runtimeConfigRoot,
|
|
542
570
|
runtimeConfigPath,
|
|
543
571
|
runtimeMcpPath,
|
|
544
572
|
gatesPath,
|
|
545
|
-
|
|
573
|
+
modelBundlePath,
|
|
546
574
|
hermesRootAuthStorePath,
|
|
547
575
|
hermesRootAuthMarkerPath,
|
|
548
576
|
modelAuthSourceFingerprint: modelAuthState.sourceFingerprint,
|
|
577
|
+
modelAuthState,
|
|
549
578
|
modelCredential,
|
|
550
579
|
};
|
|
551
580
|
}
|
|
@@ -585,7 +614,7 @@ function applicationRuntimeEnv({ desired, runtimeConfig, secrets }) {
|
|
|
585
614
|
HERMES_HOME: DATA_ROOT,
|
|
586
615
|
SELLABLE_HERMES_PROFILE_ID: desired.profileId,
|
|
587
616
|
HERMES_CONFIG_PATH: runtimeConfig.runtimeConfigPath,
|
|
588
|
-
CODEX_HOME: dirname(runtimeConfig.
|
|
617
|
+
CODEX_HOME: dirname(runtimeConfig.modelBundlePath),
|
|
589
618
|
SELLABLE_AGENT_RUNTIME: "1",
|
|
590
619
|
SELLABLE_AGENT_RUNTIME_GATE_FILE: runtimeConfig.gatesPath,
|
|
591
620
|
SLACK_HOME_CHANNEL: desired.defaultChannelId,
|
|
@@ -745,59 +774,100 @@ async function proveProvisionedApplication({
|
|
|
745
774
|
profileDigest,
|
|
746
775
|
}) {
|
|
747
776
|
const runtimeEnv = applicationRuntimeEnv({ desired, runtimeConfig, secrets });
|
|
748
|
-
const
|
|
777
|
+
const materializedModelAuth = runtimeConfig.modelAuthState.materialized;
|
|
778
|
+
const [hermesVersion, mcpVersion, hermesModelAuth] = await Promise.all([
|
|
749
779
|
run(HERMES, ["--version"], { env: runtimeEnv, timeoutMs: 180_000 }),
|
|
750
780
|
run(MCP, ["--version"], { env: runtimeEnv, timeoutMs: 180_000 }),
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
+
}),
|
|
773
804
|
]);
|
|
774
|
-
let
|
|
805
|
+
let hermesModelAuthReadback = null;
|
|
775
806
|
try {
|
|
776
|
-
|
|
807
|
+
hermesModelAuthReadback = JSON.parse(hermesModelAuth.stdout);
|
|
777
808
|
} catch {
|
|
778
|
-
|
|
809
|
+
hermesModelAuthReadback = null;
|
|
779
810
|
}
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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");
|
|
860
|
+
}
|
|
861
|
+
if (!modelAuthVerdict.accepted) {
|
|
788
862
|
throw new Error(
|
|
789
|
-
`customer_model_auth_readback_rejected:${
|
|
790
|
-
modelAuth.status !== 0
|
|
791
|
-
? "probe_failed"
|
|
792
|
-
: (modelAuthVerdict.rejection ?? "unknown")
|
|
793
|
-
}`
|
|
863
|
+
`customer_model_auth_readback_rejected:${modelAuthVerdict.rejection ?? "unknown"}`
|
|
794
864
|
);
|
|
795
865
|
}
|
|
796
866
|
if (
|
|
797
867
|
hermesVersion.status !== 0 ||
|
|
798
868
|
mcpVersion.status !== 0 ||
|
|
799
869
|
/Lazy-installing|pip install|npm install/i.test(
|
|
800
|
-
`${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}`
|
|
801
871
|
)
|
|
802
872
|
) {
|
|
803
873
|
throw new Error("customer_runtime_binary_rejected");
|
|
@@ -806,7 +876,10 @@ async function proveProvisionedApplication({
|
|
|
806
876
|
profileDigest,
|
|
807
877
|
hermesOutput: hermesVersion.stdout,
|
|
808
878
|
mcpOutput: mcpVersion.stdout,
|
|
809
|
-
modelAuthOutput:
|
|
879
|
+
modelAuthOutput: `${hermesModelAuth.stdout}\n${JSON.stringify(
|
|
880
|
+
modelAuthReadback
|
|
881
|
+
)}`,
|
|
882
|
+
modelAuthReadback,
|
|
810
883
|
};
|
|
811
884
|
}
|
|
812
885
|
|
|
@@ -935,7 +1008,13 @@ function completionReceipt({
|
|
|
935
1008
|
{ code: "NATIVE_SLACK_GATEWAY", status: "TRUE" },
|
|
936
1009
|
]),
|
|
937
1010
|
{ code: "MCP_PREBAKED", status: "TRUE" },
|
|
938
|
-
{
|
|
1011
|
+
{
|
|
1012
|
+
code:
|
|
1013
|
+
claim.operation.action === "INSTANTIATE"
|
|
1014
|
+
? "MODEL_AUTH_DESIRED_PENDING_START"
|
|
1015
|
+
: "CODEX_OAUTH_READBACK",
|
|
1016
|
+
status: "TRUE",
|
|
1017
|
+
},
|
|
939
1018
|
{ code: "NO_BOOT_INSTALL", status: "TRUE" },
|
|
940
1019
|
{ code: "DEDICATED_SLACK_APP", status: "TRUE" },
|
|
941
1020
|
],
|
|
@@ -978,7 +1057,52 @@ async function flushHermesSnapshot({ client, profileId, profileRoot }) {
|
|
|
978
1057
|
}
|
|
979
1058
|
}
|
|
980
1059
|
|
|
981
|
-
|
|
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 }) {
|
|
982
1106
|
const trustRootPem = readFileSync(TRUST_ROOT, "utf8");
|
|
983
1107
|
const { desired, toolInclude } = compileDesired(claim, trustRootPem);
|
|
984
1108
|
mkdirSync(PROFILES_ROOT, { recursive: true, mode: 0o700 });
|
|
@@ -1042,6 +1166,27 @@ async function processOperation({ claim, exchange, client }) {
|
|
|
1042
1166
|
const completed = await client.complete(receipt);
|
|
1043
1167
|
if (completed?.accepted !== true)
|
|
1044
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
|
+
}
|
|
1045
1190
|
atomicJson(join(DATA_ROOT, "sellable-agent-runtime-proof.json"), {
|
|
1046
1191
|
ok: true,
|
|
1047
1192
|
runtimeId: RUNTIME_ID,
|
|
@@ -1052,6 +1197,7 @@ async function processOperation({ claim, exchange, client }) {
|
|
|
1052
1197
|
revisionId: desired.revisionId,
|
|
1053
1198
|
observedLifecycle: receipt.observedLifecycle,
|
|
1054
1199
|
profileDigest: proof.profileDigest,
|
|
1200
|
+
modelAuth: proof.modelAuthReadback,
|
|
1055
1201
|
runtimeUid: process.getuid?.() ?? null,
|
|
1056
1202
|
runtimeGid: process.getgid?.() ?? null,
|
|
1057
1203
|
profileMode: (lstatSync(profileRoot).mode & 0o777)
|
|
@@ -1078,6 +1224,7 @@ async function processOperation({ claim, exchange, client }) {
|
|
|
1078
1224
|
trustRootPem,
|
|
1079
1225
|
action: claim.operation.action,
|
|
1080
1226
|
gateway: proof.gateway ?? null,
|
|
1227
|
+
modelHealthReporter,
|
|
1081
1228
|
};
|
|
1082
1229
|
}
|
|
1083
1230
|
|
|
@@ -1220,8 +1367,14 @@ async function main() {
|
|
|
1220
1367
|
exchange,
|
|
1221
1368
|
client,
|
|
1222
1369
|
});
|
|
1370
|
+
await activeRuntime?.modelHealthReporter?.stop();
|
|
1223
1371
|
await stopNativeGateway(activeRuntime?.gateway);
|
|
1224
|
-
activeRuntime = await processOperation({
|
|
1372
|
+
activeRuntime = await processOperation({
|
|
1373
|
+
claim,
|
|
1374
|
+
exchange,
|
|
1375
|
+
client,
|
|
1376
|
+
identity,
|
|
1377
|
+
});
|
|
1225
1378
|
operationHeartbeat.assertHealthy();
|
|
1226
1379
|
await operationHeartbeat.stop();
|
|
1227
1380
|
operationHeartbeat = null;
|
|
@@ -1256,6 +1409,7 @@ async function main() {
|
|
|
1256
1409
|
);
|
|
1257
1410
|
}
|
|
1258
1411
|
await stopNativeGateway(activeRuntime?.gateway);
|
|
1412
|
+
await activeRuntime?.modelHealthReporter?.stop();
|
|
1259
1413
|
if (activeRuntime?.desired.profileId) {
|
|
1260
1414
|
clearCustomerRuntimeSecrets({
|
|
1261
1415
|
secretsRoot: SECRET_ROOT,
|
|
@@ -1,143 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
readFileSync,
|
|
8
|
-
renameSync,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
} from "node:fs";
|
|
11
|
-
import { dirname } from "node:path";
|
|
12
|
-
|
|
13
|
-
const MARKER_VERSION = 1;
|
|
14
|
-
const MARKER_KEYS = ["generation", "sourceFingerprint", "version"];
|
|
15
|
-
|
|
16
|
-
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
17
|
-
|
|
18
|
-
function assertRegularFileOrMissing(path) {
|
|
19
|
-
if (!existsSync(path)) return;
|
|
20
|
-
const entry = lstatSync(path);
|
|
21
|
-
if (entry.isSymbolicLink() || !entry.isFile()) {
|
|
22
|
-
throw new Error("customer_model_auth_store_rejected");
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function atomicPrivateFile(path, bytes) {
|
|
27
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
28
|
-
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
29
|
-
writeFileSync(temporary, bytes, { flag: "wx", mode: 0o600 });
|
|
30
|
-
chmodSync(temporary, 0o600);
|
|
31
|
-
renameSync(temporary, path);
|
|
32
|
-
chmodSync(path, 0o600);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function readMarker(path) {
|
|
36
|
-
assertRegularFileOrMissing(path);
|
|
37
|
-
if (!existsSync(path)) return null;
|
|
38
|
-
try {
|
|
39
|
-
const marker = JSON.parse(readFileSync(path, "utf8"));
|
|
40
|
-
if (
|
|
41
|
-
!marker ||
|
|
42
|
-
typeof marker !== "object" ||
|
|
43
|
-
Array.isArray(marker) ||
|
|
44
|
-
Object.keys(marker).sort().join("\0") !==
|
|
45
|
-
[...MARKER_KEYS].sort().join("\0") ||
|
|
46
|
-
marker.version !== MARKER_VERSION ||
|
|
47
|
-
!Number.isSafeInteger(marker.generation) ||
|
|
48
|
-
marker.generation < 1 ||
|
|
49
|
-
!/^[a-f0-9]{16}$/.test(marker.sourceFingerprint)
|
|
50
|
-
) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
return marker;
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function usableHermesRootAuth(path) {
|
|
60
|
-
if (!existsSync(path)) return false;
|
|
61
|
-
try {
|
|
62
|
-
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
63
|
-
const provider = value?.providers?.["openai-codex"];
|
|
64
|
-
const pool = value?.credential_pool?.["openai-codex"];
|
|
65
|
-
return Boolean(
|
|
66
|
-
value?.active_provider === "openai-codex" &&
|
|
67
|
-
provider?.auth_mode === "chatgpt" &&
|
|
68
|
-
provider.tokens &&
|
|
69
|
-
["id_token", "access_token", "refresh_token", "account_id"].every(
|
|
70
|
-
(key) =>
|
|
71
|
-
typeof provider.tokens[key] === "string" &&
|
|
72
|
-
provider.tokens[key].length > 0
|
|
73
|
-
) &&
|
|
74
|
-
Array.isArray(pool) &&
|
|
75
|
-
pool.length > 0
|
|
76
|
-
);
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Reconcile Hermes' persistent root auth store with the sealed control-plane
|
|
84
|
-
* credential without undoing a later in-place OAuth refresh.
|
|
85
|
-
*
|
|
86
|
-
* The marker is written only after the matching auth store is durable. A
|
|
87
|
-
* missing or mismatched marker therefore means the root store must be reseeded,
|
|
88
|
-
* even when runtime-identity.json already records the new generation after an
|
|
89
|
-
* interrupted or overlapping reconciliation.
|
|
90
|
-
*/
|
|
91
|
-
export function reconcileHermesRootModelAuth({
|
|
92
|
-
authStorePath,
|
|
93
|
-
markerPath,
|
|
94
|
-
modelCredential,
|
|
95
|
-
generation,
|
|
96
|
-
}) {
|
|
97
|
-
if (
|
|
98
|
-
typeof authStorePath !== "string" ||
|
|
99
|
-
!authStorePath ||
|
|
100
|
-
typeof markerPath !== "string" ||
|
|
101
|
-
!markerPath ||
|
|
102
|
-
!Number.isSafeInteger(generation) ||
|
|
103
|
-
generation < 1 ||
|
|
104
|
-
!modelCredential ||
|
|
105
|
-
typeof modelCredential.authJson !== "string" ||
|
|
106
|
-
typeof modelCredential.hermesRootAuthJson !== "string"
|
|
107
|
-
) {
|
|
108
|
-
throw new Error("customer_model_auth_store_rejected");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
assertRegularFileOrMissing(authStorePath);
|
|
112
|
-
const sourceFingerprint = sha256(modelCredential.authJson).slice(0, 16);
|
|
113
|
-
const priorMarker = readMarker(markerPath);
|
|
114
|
-
const markerMatches =
|
|
115
|
-
priorMarker?.generation === generation &&
|
|
116
|
-
priorMarker.sourceFingerprint === sourceFingerprint;
|
|
117
|
-
const authStoreUsable = usableHermesRootAuth(authStorePath);
|
|
118
|
-
|
|
119
|
-
if (!markerMatches || !authStoreUsable) {
|
|
120
|
-
atomicPrivateFile(authStorePath, modelCredential.hermesRootAuthJson);
|
|
121
|
-
atomicPrivateFile(
|
|
122
|
-
markerPath,
|
|
123
|
-
`${JSON.stringify(
|
|
124
|
-
{
|
|
125
|
-
version: MARKER_VERSION,
|
|
126
|
-
generation,
|
|
127
|
-
sourceFingerprint,
|
|
128
|
-
},
|
|
129
|
-
null,
|
|
130
|
-
2
|
|
131
|
-
)}\n`
|
|
132
|
-
);
|
|
133
|
-
} else {
|
|
134
|
-
chmodSync(authStorePath, 0o600);
|
|
135
|
-
chmodSync(markerPath, 0o600);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return Object.freeze({
|
|
139
|
-
sourceFingerprint,
|
|
140
|
-
generation,
|
|
141
|
-
reseeded: !markerMatches || !authStoreUsable,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
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";
|