@sema-agent/server 7.100.0 → 7.101.0

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.
Files changed (102) hide show
  1. package/MIGRATION.md +22 -1
  2. package/README.md +12 -1
  3. package/README.zh-CN.md +9 -1
  4. package/USAGE.md +80 -9
  5. package/dist/approval-card.d.ts +24 -12
  6. package/dist/approval-card.js +4 -3
  7. package/dist/approval-policy-settlement.d.ts +31 -0
  8. package/dist/approval-policy-settlement.js +16 -0
  9. package/dist/approval-reconciler.d.ts +8 -1
  10. package/dist/approval-reconciler.js +5 -4
  11. package/dist/boot/config-center.js +86 -20
  12. package/dist/boot/execution-env.js +20 -2
  13. package/dist/boot/parked-revive-gate.js +1 -0
  14. package/dist/boot/reapers.js +1 -0
  15. package/dist/boot/resolve-spec.js +25 -8
  16. package/dist/boot/runner-deps.js +9 -3
  17. package/dist/boot/stage-02-registry-pull.js +9 -0
  18. package/dist/boot/stores.js +4 -0
  19. package/dist/config-catalog.js +5 -3
  20. package/dist/config-center/apply-effective.d.ts +25 -0
  21. package/dist/config-center/apply-effective.js +144 -69
  22. package/dist/config-center/apply-ledger.d.ts +12 -2
  23. package/dist/config-center/apply-ledger.js +4 -1
  24. package/dist/config-center/facade.d.ts +1 -1
  25. package/dist/config-center/facade.js +1 -1
  26. package/dist/config-center/http-client.d.ts +4 -1
  27. package/dist/config-center/http-client.js +15 -10
  28. package/dist/config-center/read-warnings.d.ts +115 -6
  29. package/dist/config-center/read-warnings.js +115 -5
  30. package/dist/config-center/types.d.ts +6 -0
  31. package/dist/config-invariants.d.ts +2 -2
  32. package/dist/config-invariants.js +15 -0
  33. package/dist/config-provider.d.ts +4 -0
  34. package/dist/config-provider.js +64 -9
  35. package/dist/config-types.d.ts +37 -8
  36. package/dist/config.js +87 -22
  37. package/dist/deployment-governance.d.ts +45 -12
  38. package/dist/deployment-governance.js +37 -9
  39. package/dist/env-name-allowlist-knobs.d.ts +37 -0
  40. package/dist/env-name-allowlist-knobs.js +24 -0
  41. package/dist/fleet-client.d.ts +2 -1
  42. package/dist/hooks/hook-llm.js +13 -4
  43. package/dist/host-lsp-manager.d.ts +14 -2
  44. package/dist/host-lsp-manager.js +7 -6
  45. package/dist/http/admission.js +22 -7
  46. package/dist/http/memory-store-refusal-reply.d.ts +24 -0
  47. package/dist/http/memory-store-refusal-reply.js +10 -0
  48. package/dist/http/routes/approvals-assistant.js +1 -0
  49. package/dist/http/routes/capabilities.js +3 -3
  50. package/dist/http/routes/memory-bundle.js +3 -0
  51. package/dist/http/routes/memory-compliance.js +3 -5
  52. package/dist/http/routes/memory-origin.js +3 -5
  53. package/dist/http/routes/sessions.js +7 -6
  54. package/dist/http/routes/side-query.js +10 -4
  55. package/dist/instruction-source-notice.d.ts +97 -0
  56. package/dist/instruction-source-notice.js +78 -0
  57. package/dist/memory-layer-legacy-lock.d.ts +14 -0
  58. package/dist/memory-layer-legacy-lock.js +74 -0
  59. package/dist/memory-operator-faces.js +1 -17
  60. package/dist/memory-store-refusal.d.ts +37 -0
  61. package/dist/memory-store-refusal.js +14 -0
  62. package/dist/model-entry-refusal.d.ts +130 -0
  63. package/dist/model-entry-refusal.js +157 -0
  64. package/dist/model-provider.d.ts +9 -3
  65. package/dist/model-route-endpoint.d.ts +1 -1
  66. package/dist/model-select.d.ts +49 -4
  67. package/dist/model-select.js +48 -17
  68. package/dist/observability/fail-open.d.ts +9 -1
  69. package/dist/observability/fail-open.js +9 -1
  70. package/dist/observability/metrics.js +1 -0
  71. package/dist/observability/secret-env-scrub.d.ts +27 -4
  72. package/dist/observability/secret-env-scrub.js +22 -7
  73. package/dist/plugins/caching-session-store.d.ts +6 -0
  74. package/dist/plugins/caching-session-store.js +6 -0
  75. package/dist/plugins/local-session-store.d.ts +20 -25
  76. package/dist/plugins/local-session-store.js +111 -64
  77. package/dist/plugins/pg-session-storage.d.ts +7 -0
  78. package/dist/plugins/pg-session-storage.js +19 -0
  79. package/dist/plugins/remote-env-host.d.ts +3 -1
  80. package/dist/plugins/remote-env-host.js +3 -6
  81. package/dist/plugins/tidb-pool.js +5 -0
  82. package/dist/plugins/tidb-session-store.d.ts +13 -0
  83. package/dist/plugins/tidb-session-store.js +14 -0
  84. package/dist/plugins/web-search.d.ts +2 -1
  85. package/dist/plugins/web-search.js +43 -24
  86. package/dist/project-memory.d.ts +10 -0
  87. package/dist/project-memory.js +28 -14
  88. package/dist/run-local.d.ts +17 -15
  89. package/dist/run-local.js +115 -15
  90. package/dist/sealed-key.d.ts +5 -2
  91. package/dist/server-secret-env.d.ts +23 -1
  92. package/dist/server-secret-env.js +9 -0
  93. package/dist/tool-approval.d.ts +65 -31
  94. package/dist/tool-approval.js +96 -49
  95. package/dist/trace/core-keyset-guard.d.ts +27 -5
  96. package/dist/trace/engine-notice-wire.d.ts +27 -1
  97. package/dist/trace/engine-notice-wire.js +8 -0
  98. package/dist/trace/wire-projection-faces.d.ts +2 -2
  99. package/dist/trace/wire-projection-faces.js +2 -2
  100. package/package.json +2 -2
  101. package/dist/memory-layer-preflight.d.ts +0 -46
  102. package/dist/memory-layer-preflight.js +0 -121
@@ -18,10 +18,18 @@ import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "../con
18
18
  import { executionLaneCaps, executionLaneOf } from "../execution-lane-caps.js";
19
19
  import { createKeyResolver } from "../key-resolver.js";
20
20
  import { ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
21
- import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, fetchPromptArtifact } from "../config-center/facade.js";
21
+ import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, withModelPlaneOf, buildEnvOnlyModelPlane, fetchPromptArtifact } from "../config-center/facade.js";
22
22
  import { createMcpRevocationLedger, enabledMcpEntries } from "../config-center/mcp-revocation.js";
23
23
  import { SHA256_HEX_RE } from "../digest-form.js";
24
- import { createConfigApplyLedger, refusedGroups } from "../config-center/apply-ledger.js";
24
+ import { createConfigApplyLedger, modelPlaneCommitted, refusedGroups } from "../config-center/apply-ledger.js";
25
+ function lkgReadNote(scope) {
26
+ switch (scope) {
27
+ case "whole":
28
+ return "booting from the persisted last-known-good (all restart-to-apply faces included) — the live pull continues in the background and reconciles on arrival";
29
+ case "model-plane":
30
+ return "the live candidate's model plane was refused at boot — the persisted last-known-good supplies the model plane only (catalog and the collab templates that reference it); every other group and every restart-to-apply face stays the live candidate's";
31
+ }
32
+ }
25
33
  export async function createConfigCenterRuntime(ctx) {
26
34
  const { config, logger, metrics, localRoot } = ctx;
27
35
  let ccEtag;
@@ -114,6 +122,18 @@ export async function createConfigCenterRuntime(ctx) {
114
122
  : undefined;
115
123
  const rosterGateArmed = isRosterGateArmed(cc);
116
124
  const modelReadyState = { ready: !rosterGateArmed };
125
+ const warnDroppedEntries = (r, phase) => {
126
+ for (const d of r.droppedEntries ?? []) {
127
+ logger.warn("config_entry_dropped", {
128
+ domain: d.domain,
129
+ entry: d.entry,
130
+ error: d.error,
131
+ version: r.effective.version,
132
+ phase,
133
+ note: "this one entry failed its domain schema and was dropped on its own — the rest of the candidate applies (per-entry tolerance); fix the named entry to bring it back",
134
+ });
135
+ }
136
+ };
117
137
  const markRosterLanded = (eff) => {
118
138
  if (!modelReadyState.ready && (eff.models?.models ?? []).some((m) => m.enabled !== false)) {
119
139
  modelReadyState.ready = true;
@@ -312,7 +332,7 @@ export async function createConfigCenterRuntime(ctx) {
312
332
  };
313
333
  if (configProvider) {
314
334
  const dryRun = cc?.dryRun ?? false;
315
- const bootFromLkg = async (why) => {
335
+ const bootFromLkg = async (why, scope) => {
316
336
  if (!lkgEnabled || dryRun)
317
337
  return null;
318
338
  const identity = currentIdentity();
@@ -344,7 +364,7 @@ export async function createConfigCenterRuntime(ctx) {
344
364
  logger.warn("config_lkg_unusable", { path: lkgPath, reason: "center credential identity changed while the LKG was being read — not used", note: "env fallback serves; the current identity's next pull re-establishes its own LKG" });
345
365
  return null;
346
366
  }
347
- logger.info("config_loaded_lkg", { path: lkgPath, savedAt: read.lkg.savedAt, why, note: "booting from the persisted last-known-good (all restart-to-apply faces included) — the live pull continues in the background and reconciles on arrival" });
367
+ logger.info("config_loaded_lkg", { path: lkgPath, savedAt: read.lkg.savedAt, why, scope, note: lkgReadNote(scope) });
348
368
  const lkgEffective = read.lkg.effective;
349
369
  candidateIdentity.set(lkgEffective, identity);
350
370
  bindValidator(read.lkg.etag, identity);
@@ -353,18 +373,31 @@ export async function createConfigCenterRuntime(ctx) {
353
373
  const bootApplyLkgInline = async (lkgR) => {
354
374
  const lkgGen = generationOf(lkgR.effective.version);
355
375
  applyLedger.seenTarget(lkgGen, lkgR.effective.version);
356
- applyEffective(config, lkgR.effective, logger, { leg: "boot", sealedKeys, generation: lkgGen, report: (rep) => applyLedger.recordApply(rep) });
376
+ let lkgReport;
377
+ applyEffective(config, lkgR.effective, logger, {
378
+ leg: "boot",
379
+ sealedKeys,
380
+ generation: lkgGen,
381
+ report: (rep) => {
382
+ lkgReport = rep;
383
+ applyLedger.recordApply(rep);
384
+ },
385
+ });
357
386
  applyLedger.applied(lkgGen, lkgR.effective.version);
358
387
  await adoptCenterPrompts(lkgR.effective, "boot-lkg");
359
- markRosterLanded(lkgR.effective);
388
+ const lkgPlane = modelPlaneCommitted(lkgReport);
389
+ if (lkgPlane)
390
+ markRosterLanded(lkgR.effective);
360
391
  effective = lkgR.effective;
361
- appliedPlaneEff = lkgR.effective;
392
+ if (lkgPlane)
393
+ appliedPlaneEff = lkgR.effective;
362
394
  latestEffective = lkgR.effective;
363
395
  if (lkgR.etag !== undefined)
364
396
  ccEtag = lkgR.etag;
365
397
  const cfgModels = (lkgR.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
366
398
  logger.info("config_loaded", { source: "lkg", version: lkgR.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels });
367
399
  };
400
+ let bootApplyPhase = false;
368
401
  try {
369
402
  const bootFetch = pullEffective(configProvider, undefined);
370
403
  const raced = await raceBootFetch(bootFetch, config.configBootFetchBudgetMs);
@@ -372,7 +405,7 @@ export async function createConfigCenterRuntime(ctx) {
372
405
  if (raced === BOOT_FETCH_DEFERRED) {
373
406
  bootConfigPending = bootFetch;
374
407
  bootFetch.catch(() => { });
375
- lkgFallback = await bootFromLkg("boot fetch deferred (budget exceeded)");
408
+ lkgFallback = await bootFromLkg("boot fetch deferred (budget exceeded)", "whole");
376
409
  lkgBooted = lkgFallback !== null;
377
410
  logger.warn("config_boot_fetch_deferred", {
378
411
  source: configProvider.kind,
@@ -394,11 +427,13 @@ export async function createConfigCenterRuntime(ctx) {
394
427
  }
395
428
  const r = raced === BOOT_FETCH_DEFERRED ? null : raced;
396
429
  if (r) {
430
+ bootApplyPhase = true;
397
431
  const bootPromptsRaw = r.effective.prompts;
398
432
  const bootPromptsOk = bootPromptsRaw === undefined || validatePromptsDomain(bootPromptsRaw).ok;
399
433
  let bootClean = (r.domainErrors ?? []).length === 0 && bootPromptsOk;
400
434
  for (const de of r.domainErrors ?? [])
401
435
  logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: "tolerated at BOOT (corrupt file → schema defaults; dangling ref → reported as-is); the refresh path rejects such a candidate whole and keeps the LKG (§9.5-5)" });
436
+ warnDroppedEntries(r, "boot");
402
437
  if (dryRun) {
403
438
  logEffectiveDiff(config, r.effective, logger);
404
439
  if (bootClean)
@@ -423,9 +458,23 @@ export async function createConfigCenterRuntime(ctx) {
423
458
  if (bootClean)
424
459
  ccEtag = r.etag;
425
460
  await adoptCenterPrompts(r.effective, "boot");
426
- markRosterLanded(r.effective);
427
- effective = r.effective;
428
- appliedPlaneEff = r.effective;
461
+ let bootPlaneEff = modelPlaneCommitted(bootReport) ? r.effective : undefined;
462
+ if (bootPlaneEff === undefined) {
463
+ const lkgPlane = await bootFromLkg("boot candidate's model plane was refused", "model-plane");
464
+ if (lkgPlane) {
465
+ let planeReport;
466
+ applyEffective(config, withModelPlaneOf(r.effective, lkgPlane.effective), logger, { leg: "boot", sealedKeys, generation: bootGen, report: (rep) => (planeReport = rep) });
467
+ if (modelPlaneCommitted(planeReport)) {
468
+ bootPlaneEff = lkgPlane.effective;
469
+ logger.warn("config_model_plane_from_lkg", { version: r.effective.version, lkgVersion: lkgPlane.effective.version, note: "the live candidate's model plane was refused at boot — serving the LKG's model plane (catalog and the collab templates that reference it) with the live candidate's other groups; the refresh cadence re-judges the candidate" });
470
+ }
471
+ }
472
+ }
473
+ if (bootPlaneEff !== undefined)
474
+ markRosterLanded(bootPlaneEff);
475
+ effective = bootPlaneEff === r.effective ? r.effective : withModelPlaneOf(r.effective, bootPlaneEff ?? buildEnvOnlyModelPlane());
476
+ if (bootPlaneEff !== undefined)
477
+ appliedPlaneEff = bootPlaneEff;
429
478
  if (bootClean)
430
479
  latestEffective = r.effective;
431
480
  if (bootClean && !lkgBooted)
@@ -435,23 +484,30 @@ export async function createConfigCenterRuntime(ctx) {
435
484
  logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels, ...cfgDir });
436
485
  if (configProvider.kind === "local" && cfgModels === 0) {
437
486
  const modelsDomainErr = (r.domainErrors ?? []).find((de) => de.domain === "models");
487
+ const modelsDropped = (r.droppedEntries ?? []).filter((d) => d.domain === "models");
438
488
  const totalModels = (r.effective.models?.models ?? []).length;
439
489
  logger.warn("config_local_no_models", {
440
490
  ...cfgDir,
441
491
  ...(modelsDomainErr ? { domainError: modelsDomainErr.error } : {}),
492
+ ...(modelsDropped.length > 0 ? { droppedEntries: modelsDropped.map((d) => d.entry) } : {}),
442
493
  hint: modelsDomainErr
443
494
  ? "config.d/models.json 在场但 models 域判形失败落 default(真因见本行 domainError)— 模型继续走 env 兜底"
444
- : totalModels > 0
445
- ? `config.d/models.json 有 ${totalModels} 条模型但全部 enabled:false — 模型继续走 env 兜底`
446
- : "config.d/models.json 空或 CONFIG_LOCAL_DIR 指错目录(选目录旋钮是 CONFIG_LOCAL_DIR,不是 LOCAL_DATA_ROOT)— 模型继续走 env 兜底",
495
+ : modelsDropped.length > 0 && totalModels === 0
496
+ ? "config.d/models.json 的条目全部被逐条丢弃(逐条真因见 config_entry_dropped)— 模型继续走 env 兜底"
497
+ : totalModels > 0
498
+ ? `config.d/models.json 有 ${totalModels} 条模型但全部 enabled:false — 模型继续走 env 兜底`
499
+ : "config.d/models.json 空或 CONFIG_LOCAL_DIR 指错目录(选目录旋钮是 CONFIG_LOCAL_DIR,不是 LOCAL_DATA_ROOT)— 模型继续走 env 兜底",
447
500
  });
448
501
  }
449
502
  }
450
503
  }
451
504
  }
452
505
  catch (err) {
453
- logger.warn("config_source_unreachable_using_env", { source: configProvider.kind, err: String(err) });
454
- const lkgFallback = await bootFromLkg(`boot fetch failed: ${String(err).slice(0, 120)}`);
506
+ if (bootApplyPhase)
507
+ logger.warn("config_apply_refused", { source: configProvider.kind, err: String(err), note: "the pulled candidate failed to apply at boot — falling back to the LKG / env; this is not a reachability failure" });
508
+ else
509
+ logger.warn("config_source_unreachable_using_env", { source: configProvider.kind, err: String(err) });
510
+ const lkgFallback = await bootFromLkg(`${bootApplyPhase ? "boot apply refused" : "boot fetch failed"}: ${String(err).slice(0, 120)}`, "whole");
455
511
  if (lkgFallback) {
456
512
  lkgBooted = true;
457
513
  try {
@@ -696,6 +752,7 @@ export async function createConfigCenterRuntime(ctx) {
696
752
  ...(r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })),
697
753
  ...(promptsGate.ok ? [] : [{ domain: "prompts", error: promptsGate.error }]),
698
754
  ],
755
+ ...(r.droppedEntries !== undefined ? { droppedEntries: r.droppedEntries } : {}),
699
756
  lkgVersion: latestEffective?.version ?? "(env)",
700
757
  note: "candidate rejected whole (§9.5-5) — live config keeps the last-known-good; fix the named files to unblock",
701
758
  });
@@ -703,6 +760,7 @@ export async function createConfigCenterRuntime(ctx) {
703
760
  return "ok";
704
761
  }
705
762
  lastRejectedCandidate = undefined;
763
+ warnDroppedEntries(r, "refresh");
706
764
  if (ccRef?.dryRun) {
707
765
  logEffectiveDiff(config, r.effective, logger);
708
766
  ccEtag = r.etag;
@@ -730,10 +788,13 @@ export async function createConfigCenterRuntime(ctx) {
730
788
  mcpRevocations.applyEnabledEntries(enabledMcpEntries(r.effective.mcp));
731
789
  applyLiveReadFace(r.effective, swapRunnerDeps, "refresh");
732
790
  applyLedger.applied(tickGen, r.effective.version);
733
- appliedPlaneEff = r.effective;
791
+ const tickPlane = modelPlaneCommitted(applyReport);
792
+ if (tickPlane)
793
+ appliedPlaneEff = r.effective;
734
794
  planeDeferredNoHandoff = undefined;
735
795
  await adoptCenterPrompts(r.effective, "refresh");
736
- markRosterLanded(r.effective);
796
+ if (tickPlane)
797
+ markRosterLanded(r.effective);
737
798
  const fullyApplied = applyReport?.fullyApplied !== false;
738
799
  if (!fullyApplied) {
739
800
  ccEtag = undefined;
@@ -826,10 +887,12 @@ export async function createConfigCenterRuntime(ctx) {
826
887
  logger.warn("config_boot_deferred_candidate_rejected", {
827
888
  version: r.effective.version,
828
889
  errors: [...(r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })), ...(promptsGate.ok ? [] : [{ domain: "prompts", error: promptsGate.error }])],
890
+ ...(r.droppedEntries !== undefined ? { droppedEntries: r.droppedEntries } : {}),
829
891
  note: "late boot pull carried an invalid candidate — env fallback keeps serving; the refresh cadence re-judges the same tree (etag not advanced)",
830
892
  });
831
893
  return;
832
894
  }
895
+ warnDroppedEntries(r, "boot-deferred");
833
896
  if (ccRef?.dryRun) {
834
897
  logEffectiveDiff(config, r.effective, logger);
835
898
  ccEtag = r.etag;
@@ -856,10 +919,13 @@ export async function createConfigCenterRuntime(ctx) {
856
919
  ceilingVerdict?.judge();
857
920
  applyLiveReadFace(r.effective, swapRunnerDeps, "boot-deferred");
858
921
  applyLedger.applied(lateGen, r.effective.version);
859
- appliedPlaneEff = r.effective;
922
+ const latePlane = modelPlaneCommitted(applyReportLate);
923
+ if (latePlane)
924
+ appliedPlaneEff = r.effective;
860
925
  planeDeferredNoHandoff = undefined;
861
926
  await adoptCenterPrompts(r.effective, "boot-deferred");
862
- markRosterLanded(r.effective);
927
+ if (latePlane)
928
+ markRosterLanded(r.effective);
863
929
  const fullyAppliedLate = applyReportLate?.fullyApplied !== false;
864
930
  if (!fullyAppliedLate) {
865
931
  ccEtag = undefined;
@@ -29,6 +29,24 @@ function boundedSessionSetter(map, maxEntries) {
29
29
  map.delete(map.keys().next().value);
30
30
  };
31
31
  }
32
+ function forwardedSandboxEnvKeys(remoteExec) {
33
+ switch (remoteExec.provider) {
34
+ case "e2b":
35
+ return Object.keys(remoteExec.sandboxEnv ?? {}).sort();
36
+ case "local-docker":
37
+ return Object.keys(remoteExec.env ?? {}).sort();
38
+ case "k8s":
39
+ case "ssh":
40
+ case "adb":
41
+ case "host":
42
+ case "device":
43
+ return [];
44
+ default: {
45
+ const unreachable = remoteExec;
46
+ throw new Error(`unhandled remote-exec provider: ${String(unreachable.provider)}`);
47
+ }
48
+ }
49
+ }
32
50
  const MAX_CWD_SESSIONS = 4096;
33
51
  export function createSessionScopedRegistries(maxEntries = MAX_CWD_SESSIONS) {
34
52
  const perSessionCwd = new Map();
@@ -275,7 +293,7 @@ export function createExecutionEnv(ctx) {
275
293
  config.remoteExec.provider === "local-docker";
276
294
  const suspendable = config.remoteExec.provider === "e2b" ||
277
295
  (config.remoteExec.provider === "k8s" && !!config.remoteExec.s3Snapshot);
278
- const sandboxEnvKeys = config.remoteExec.provider === "e2b" ? Object.keys(config.remoteExec.sandboxEnv ?? {}) : [];
296
+ const sandboxEnvKeys = forwardedSandboxEnvKeys(config.remoteExec);
279
297
  logger.info("remote_exec_enabled", { provider: config.remoteExec.provider, isolated, suspendable, ...(sandboxEnvKeys.length > 0 ? { sandboxEnvKeys } : {}) });
280
298
  if (!isolated)
281
299
  logger.warn("remote_exec_real_system", { provider: config.remoteExec.provider, note: "non-isolated target — actions are permanent; rely on the policy gate + HITL (design/61 §5)" });
@@ -290,7 +308,7 @@ export function createExecutionEnv(ctx) {
290
308
  })
291
309
  :
292
310
  lspProvider === "host" && config.lspHostEnabled && config.requirePrincipal !== true
293
- ? createHostLspManager((event, fields) => logger.info(event, fields))
311
+ ? createHostLspManager(config, (event, fields) => logger.info(event, fields))
294
312
  : undefined;
295
313
  if (lspManager)
296
314
  logger.info("lsp_enabled", { provider: lspProvider });
@@ -29,6 +29,7 @@ export function createParkedReviveInheritedGate(deps) {
29
29
  : { env: createHostAdjudicationEnv(config, localRoot), ...homeBase };
30
30
  const rootSessionId = row.rootSessionId;
31
31
  const baseline = createApprovalBaselinePolicy(config, {
32
+ kind: "durable",
32
33
  question,
33
34
  ...(approvalExemptionStore && rootSessionId
34
35
  ? {
@@ -120,6 +120,7 @@ export function startReapers(ctx) {
120
120
  pendingGraceMs: config.streamApproval.pendingGraceMs,
121
121
  adhocGraceMs: config.streamApproval.adhocGraceMs,
122
122
  orphanTtlMs: config.streamApproval.orphanTtlMs,
123
+ unattendedPolicy: config.unattendedApprovalPolicy,
123
124
  ...(toolApproval ? { emitRevoke: (frame, target) => void toolApproval.emitRevoke(frame, target) } : {}),
124
125
  });
125
126
  })();
@@ -17,7 +17,8 @@ import { composeHooks, createTaskHooks } from "../hooks/hook-runner.js";
17
17
  import { HOST_POSTURE_REFUSED_CODE, taskApproverPostureHonored, taskWriteFaceRefusal } from "../hosted-posture.js";
18
18
  import { headerStr } from "../http/principal-gate.js";
19
19
  import { explicitOperatorOk } from "../http/server.js";
20
- import { isModelAllowlisted, matchCatalogModel, modelSupportsImages, resolveTaskModel, terminalCatalogName } from "../model-select.js";
20
+ import { isModelAllowlisted, matchCatalogModel, modelSupportsImages, refusedCatalogEntry, resolveTaskModel, selectableCatalog, terminalCatalogName } from "../model-select.js";
21
+ import { modelUnavailableMessage } from "../model-entry-refusal.js";
21
22
  import { PerTaskImageRegistry, resolveSandboxImageRef } from "../per-task-image.js";
22
23
  import { isRemoteScratchpadLane, remoteScratchpadDirFor } from "../plugins/remote-scratchpad.js";
23
24
  import { bindAttachmentsForTask } from "../plugins/task-attachment-store.js";
@@ -43,7 +44,10 @@ import { memoryFaceMounted } from "../memory-scope.js";
43
44
  export function createResolveSpec(ctx) {
44
45
  const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, noteSessionShellGate, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, } = ctx;
45
46
  assertGuardPatternsUsable(config);
46
- const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, { durableEnabled, singleUserAutoAcceptBaseline: singleUserAutoAcceptBaselineApplies(config) });
47
+ const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, {
48
+ approvalSeat: durableEnabled ? "durable" : "absent",
49
+ singleUserAutoAcceptBaseline: singleUserAutoAcceptBaselineApplies(config),
50
+ });
47
51
  const realTaskRootOf = (auth) => {
48
52
  if (isSandboxPathAdjudicationLane(config.remoteExec?.provider))
49
53
  return undefined;
@@ -217,8 +221,16 @@ export function createResolveSpec(ctx) {
217
221
  if (rawAddReadDirs !== undefined && !cwdHonored(config)) {
218
222
  logger.warn("task_additional_read_directories_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
219
223
  }
220
- const wireCatalog = expandTiers(config.models, config.tiers) ?? config.models;
221
- const picked = resolveTaskModel(body.model ?? parsedSettings.settings?.model, objective, wireCatalog, config.atModelAllowlist);
224
+ const wireCatalog = selectableCatalog(config.models, config.tiers, config.modelRefusals);
225
+ const picked = resolveTaskModel(body.model ?? parsedSettings.settings?.model, objective, wireCatalog, config.atModelAllowlist, config.modelRefusals);
226
+ if (picked.unavailable !== undefined) {
227
+ const { requested, name, refusal, source } = picked.unavailable;
228
+ if (opts?.leg === "fresh") {
229
+ throw new HttpError(400, modelUnavailableMessage(source === "mention" ? "@-mention" : "model", requested.slice(0, 120), name, refusal), { code: "request.model_unavailable" });
230
+ }
231
+ metrics.inc("task_model_unavailable_fallback_total");
232
+ logger.warn("task_model_unavailable_fallback", { requested: requested.slice(0, 120), name, code: refusal.code, source, fallback: picked.model, sessionId: auth?.sessionId ?? null });
233
+ }
222
234
  if (picked.notAllowed !== undefined) {
223
235
  const { requested, resolved, source } = picked.notAllowed;
224
236
  if (opts?.leg === "fresh") {
@@ -340,7 +352,11 @@ export function createResolveSpec(ctx) {
340
352
  ...((() => {
341
353
  if (typeof body.compactionModel !== "string" || body.compactionModel.length === 0)
342
354
  return {};
343
- const cm = matchCatalogModel(body.compactionModel, wireCatalog);
355
+ const cm = matchCatalogModel(body.compactionModel, wireCatalog, config.modelRefusals);
356
+ const refusedCm = cm === undefined ? refusedCatalogEntry(body.compactionModel, config.modelRefusals) : undefined;
357
+ if (refusedCm !== undefined && opts?.leg === "fresh") {
358
+ throw new HttpError(400, modelUnavailableMessage("compactionModel", body.compactionModel.slice(0, 120), refusedCm.name, refusedCm.refusal), { code: "request.model_unavailable" });
359
+ }
344
360
  if (cm === undefined) {
345
361
  if (opts?.leg === "fresh") {
346
362
  throw new HttpError(400, `unknown compactionModel "${body.compactionModel.slice(0, 120)}" — not in the configured catalog (name, tier word, or id; a catalog refresh may have removed it mid-request)`);
@@ -349,8 +365,8 @@ export function createResolveSpec(ctx) {
349
365
  logger.warn("compaction_model_unknown_fallback", { requested: body.compactionModel.slice(0, 120), sessionId: auth?.sessionId ?? null });
350
366
  return {};
351
367
  }
352
- if (!isModelAllowlisted(cm, wireCatalog, config.atModelAllowlist)) {
353
- const terminal = terminalCatalogName(cm, wireCatalog) ?? cm;
368
+ if (!isModelAllowlisted(cm, wireCatalog, config.atModelAllowlist, config.modelRefusals)) {
369
+ const terminal = terminalCatalogName(cm, wireCatalog, config.modelRefusals) ?? cm;
354
370
  if (opts?.leg === "fresh") {
355
371
  throw new HttpError(400, `compactionModel "${body.compactionModel.slice(0, 120)}" is not in this deployment's @-mention allowlist (it resolves to catalog model "${terminal}") — the allowlist is configured by the operator (config-center models.atModelAllowlist)`, { code: "request.model_not_allowed" });
356
372
  }
@@ -414,6 +430,7 @@ export function createResolveSpec(ctx) {
414
430
  promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
415
431
  toolPolicy: toolPolicyForHands(hands, durableEnabled
416
432
  ? createApprovalBaselinePolicy(config, {
433
+ kind: "durable",
417
434
  question: liveQuestionFace,
418
435
  ...(approvalExemptionStore && auth?.sessionId
419
436
  ? {
@@ -423,7 +440,7 @@ export function createResolveSpec(ctx) {
423
440
  : {}),
424
441
  })
425
442
  : singleUserAutoAcceptBaselineApplies(config)
426
- ? createApprovalBaselinePolicy(config)
443
+ ? createApprovalBaselinePolicy(config, { kind: "absent" })
427
444
  : undefined),
428
445
  ...(durableEnabled
429
446
  ? {
@@ -11,9 +11,11 @@ import { resolveServedSession } from "../orchestration/workflow-completion-inbox
11
11
  import { defaultEngineNoticeRouter } from "../trace/engine-notice-wire.js";
12
12
  import { WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
13
13
  import { makeLoadProjectMemory, makeProbeInstructionSources } from "../project-memory.js";
14
+ import { createInstructionSourceSeat } from "../instruction-source-notice.js";
14
15
  import { workflowModelAllowlistFor } from "../task-workflow.js";
15
16
  import { buildCrossSessionInboundLayers, crossSessionDialogExpiryOf } from "../cross-session-settings.js";
16
17
  import { createPeerDirectory } from "../peer-directory.js";
18
+ import { targetOutsideStoreInIncident } from "../memory-store-refusal.js";
17
19
  export function createRunnerDepsOnAsk(toolApproval) {
18
20
  if (!toolApproval)
19
21
  return undefined;
@@ -188,8 +190,10 @@ export function createRunnerDeps(ctx) {
188
190
  metrics.inc("memory_harvest_total", { ok: String(report.ok), phase: info.phase, incident: report.incident?.kind ?? "none" });
189
191
  if (report.patches)
190
192
  metrics.inc("memory_harvest_patches_total", { phase: info.phase }, (report.patches.add ?? 0) + (report.patches.update ?? 0));
191
- if (report.incident)
192
- logger.warn("memory_harvest_incident", { kind: report.incident.kind, phase: info.phase });
193
+ if (report.incident) {
194
+ const code = targetOutsideStoreInIncident(report.incident);
195
+ logger.warn("memory_harvest_incident", { kind: report.incident.kind, phase: info.phase, ...(code !== undefined ? { code } : {}) });
196
+ }
193
197
  const byCode = new Map();
194
198
  for (const r of [...(report.rejections ?? []), ...(report.inboundFindings ?? [])])
195
199
  byCode.set(r.code, (byCode.get(r.code) ?? 0) + 1);
@@ -209,7 +213,9 @@ export function createRunnerDeps(ctx) {
209
213
  fileHistoryStore: fileHistoryStore ? fileHistoryStore : undefined,
210
214
  permissionRuleStore: permissionRuleStore ? permissionRuleStore : undefined,
211
215
  onBackgroundChildEvent: fleetBackgroundChildPublisher(fleetBus, (msg, fields) => logger.info(msg, fields)),
212
- loadProjectMemory: config.requirePrincipal !== true && config.projectMemoryEnabled ? makeLoadProjectMemory({ logger }) : undefined,
216
+ loadProjectMemory: config.requirePrincipal !== true && config.projectMemoryEnabled
217
+ ? makeLoadProjectMemory({ logger, instructionSource: createInstructionSourceSeat(sessionStore, sharedRunnerDeps.onNotice) })
218
+ : undefined,
213
219
  probeInstructionSources: config.remoteExec?.provider === "host" && config.requirePrincipal !== true && config.projectMemoryEnabled
214
220
  ? makeProbeInstructionSources()
215
221
  : undefined,
@@ -1,8 +1,17 @@
1
1
  import { createUnpricedCeilingVerdict } from "./unpriced-ceiling-verdict.js";
2
2
  import { createConfigCenterRuntime } from "./config-center.js";
3
+ import { defaultModelRefusal, refusedNameMintedRefusal } from "../model-entry-refusal.js";
3
4
  export async function runStage02(ctx) {
4
5
  const { config, localRoot, logger, metrics } = ctx;
5
6
  const configCenter = await createConfigCenterRuntime({ config, logger, metrics, localRoot });
7
+ const noDefaultModel = defaultModelRefusal(config, "env");
8
+ if (noDefaultModel !== undefined) {
9
+ throw new Error(noDefaultModel);
10
+ }
11
+ const mintedRefused = refusedNameMintedRefusal(config);
12
+ if (mintedRefused !== undefined) {
13
+ throw new Error(mintedRefused);
14
+ }
6
15
  await configCenter.applyLocalRemoteExec();
7
16
  const ceilingVerdict = createUnpricedCeilingVerdict({ config, logger });
8
17
  ceilingVerdict.judge();
@@ -24,6 +24,8 @@ import { assertDeviceAuditRebindEventSchema, ensurePgDeviceSchema, ensureTiDBDev
24
24
  import { mysqlDriver, pgDriver } from "../plugins/sql-driver.js";
25
25
  import { assertCloudSnapshotBlobPosture, openStoreBackendWithFallback } from "../plugins/store-backend.js";
26
26
  import { createMemoryEngineIncidentSink } from "../memory-incident-sink.js";
27
+ import { announceLockedLegacyMemoryLayers } from "../memory-layer-legacy-lock.js";
28
+ import { createEngineNoticeForwarder } from "./runner-deps.js";
27
29
  import { buildMemoryRemoteLaneWarn, memoryEngineBackendFor, memoryEngineRemoteLanePosture, memoryEngineRootFor, memoryMountRootOf } from "../memory-scope.js";
28
30
  import { assertToolResultProvenanceSchema } from "../plugins/tool-result-store-sql.js";
29
31
  import { assertPermissionRuleApprovalSchema, assertPermissionRuleTicketSchema } from "../plugins/permission-rule-store-sql.js";
@@ -186,6 +188,8 @@ export async function openStores(ctx) {
186
188
  }
187
189
  else {
188
190
  memoryEngine = memoryEngineBackendFor(config, undefined, createMemoryEngineIncidentSink({ logger, metrics }));
191
+ if (memoryEngine)
192
+ announceLockedLegacyMemoryLayers(memoryMountRootOf(memoryEngine.backend, memoryEngine.root), createEngineNoticeForwarder(logger));
189
193
  if (memoryEngine && process.env.LOCAL_DATA_ROOT && !config.memoryEngineDir) {
190
194
  logger.warn("memory_engine_dir_defaulted", {
191
195
  root: memoryEngine.root,
@@ -7,6 +7,7 @@ import { REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
7
7
  import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
8
8
  import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
9
9
  import { buildDisplaySafeUrl } from "./trace/redact.js";
10
+ import { describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
10
11
  export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
11
12
  export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
12
13
  export const CATALOG_DANGER_AXES = ["security", "billing", "data"];
@@ -125,7 +126,7 @@ export const CONFIG_CATALOG = [
125
126
  r("DOCKER_NETWORK", "orchestration", "enum", "local-docker 容器网络模式", { enumValues: ["none", "bridge", "host"], staticDefault: "bridge", danger: SEC }),
126
127
  r("DOCKER_PATH", "orchestration", "path", "docker 二进制路径"),
127
128
  r("DOCKER_PIDS_LIMIT", "orchestration", "number", "local-docker fork-bomb 背栓(--pids-limit;非法值回 adapter 默认 512+warn)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ adapter 默认 512" }),
128
- r("DOCKER_SANDBOX_ENV", "orchestration", "csv", "转发进容器的本进程 env **名字**表(值只在 worker env,不进 spec/center)", { danger: SEC }),
129
+ r("DOCKER_SANDBOX_ENV", "orchestration", "secret", "local-docker 车道每条 docker exec 以 -e 注入的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值不进 spec/center/日志;与 LSP_ENV_ALLOW / E2B_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)", { danger: SEC }),
129
130
  r("DRAIN_GRACE_MS", "limitsHttp", "number", "SIGTERM 优雅排空窗(ms;期间新计费提交 503+Retry-After)。出厂默认是**服务器部署**的取值;壳自 spawn 的引擎会被壳注入更短的窗 ⇒ 排壳形关机预算时以本行的 effectiveValue(= 本进程实际生效值)为准,不照抄 staticDefault", { staticDefault: String(10 * 60 * 1000), configKey: "drainGraceMs", center: cLimits("drainGraceMs") }),
130
131
  r("DURABLE_APPROVAL", "approval", "boolean", "durable suspend/resume HITL(需 SQL 店;决定 checkpointStore 与 /v1/approvals 面的存在性)", { danger: SEC, derivedDefaultNote: "posture 三态(S-178):显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)且有耐久店 ⇒ on / 多租户或无耐久店 ⇒ off;生效来源见 GET /v1/diagnostics/wiring 的 serverGates.durableApproval.source", configKey: "durableApproval" }),
131
132
  r("E2B_ALLOW_NET", "orchestration", "boolean", "e2b 沙箱出网许可", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 不铸键(adapter 默认允许)" }),
@@ -136,7 +137,7 @@ export const CONFIG_CATALOG = [
136
137
  r("E2B_LIVENESS_MS", "orchestration", "number", "e2b 单命令静默活性上限(长静默构建请调大;后台+轮询仍是首选)", { derivedDefaultNote: "缺席 ⇒ adapter 默认 120s" }),
137
138
  r("E2B_MOUNT_PATH", "orchestration", "path", "e2b 工作区根(resume 用当前值,改前先 drain 挂起任务)"),
138
139
  r("E2B_ORPHAN_RECLAIM", "orchestration", "boolean", "崩溃遗留沙盒回收腿(boot 一次+周期;判据保守,拿不准恒不杀)", { staticDefault: "true", danger: BILL }),
139
- r("E2B_SANDBOX_ENV", "orchestration", "csv", "转发进 e2b 沙箱的本进程 env 名字表(值不进 spec/center/事件日志)", { danger: SEC }),
140
+ r("E2B_SANDBOX_ENV", "orchestration", "secret", "转发进 e2b 沙箱的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值不进 spec/center/事件日志;与 LSP_ENV_ALLOW / DOCKER_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)", { danger: SEC }),
140
141
  r("E2B_SANDBOX_MAX_MS", "orchestration", "number", "e2b 沙箱最长存活(ms)", { staticDefault: "3600000", danger: BILL }),
141
142
  r("E2B_TEMPLATE", "orchestration", "string", "e2b 模板名(缺省 adapter 基础模板)"),
142
143
  r("E2B_TIMEOUT_MS", "orchestration", "number", "e2b 单命令总超时(ms)"),
@@ -208,6 +209,7 @@ export const CONFIG_CATALOG = [
208
209
  r("LOCKED_CONFIG_KEYS", "auth", "csv", "管理员锁定层声明(词表=core 闭集注册表,未知键拒启)", { danger: SEC, configKey: "lockedConfigKeys" }),
209
210
  r("LOG_LEVEL", "observability", "string", "日志档位", { staticDefault: "info", configKey: "logLevel" }),
210
211
  r("LSP_ENABLED", "orchestration", "boolean", "LSP 沙箱腿 opt-in(需烤好的 LSP 模板;host 腿另有 LSP_HOST_ENABLED)", { staticDefault: "false", configKey: "lspEnabled" }),
212
+ r("LSP_ENV_ALLOW", "orchestration", "secret", `host 腿语言服务器子进程 env 的点名放行名单(点到的键从服务 env 原样放回、部署方显式承担;没点到的照剥;与 host shell inheritEnv:[names] 同一只谓词;形错 / 点名名单型旋钮(自己或两只沙箱名单)拒启且不回显值;本进程环境里没有的名按序号 warn、不放行、不回显;多租户姿态(忽略时起服 warn 一次):${describeAllowlistPostures()};原文按凭据类对待:只回在场位、不进任何子进程,生效名单见起服披露行 allowed)`, { danger: SEC, derivedDefaultNote: "缺席 / 空串 ⇒ 空名单 = 全剥(7.100.0 行为)" }),
211
213
  r("LSP_HOST_ENABLED", "orchestration", "boolean", "LSP host 腿开关(本机 language server,优雅降级)", { staticDefault: "true", configKey: "lspHostEnabled" }),
212
214
  r("MANUAL_MODE_SHELL_GATE", "approval", "enum", "手动模式 shell 门收紧腿(off=no-op 非放松 —— 本键只有收紧半场;拼错拒启)", { danger: SEC, enumValues: ["always", "classify", "off"], echoWords: ["always", "classify"], resolve: (c) => c.manualModeShellGate ?? null, derivedDefaultNote: "off/未设 ⇒ 本键不贡献任何东西(归一成键缺席)⇒ 回显 null;真姿态只有 always/classify 两个" }),
213
215
  r("MAX_PRINCIPAL_COST_USD", "limitsHttp", "number", "per-principal 窗内成本天花板(0=关)", { staticDefault: "0", danger: BILL, configKey: "maxPrincipalCostUsd", center: cLimits("maxPrincipalCostUsd") }),
@@ -453,7 +455,7 @@ export const CONFIG_CATALOG = [
453
455
  r("UNREACHED_ASK_TTL_MS", "approval", "number", "零活流铸造的 ask 悬挂窗(ms;[60s,24h] 拒启带界)", { staticDefault: "3600000" }),
454
456
  r("USAGE_WINDOWS", "orchestration", "opaque", "部署级治理窗(JSON 数组;滑动窗内跨任务 token/$ 预算,key=principal)", { danger: BILL }),
455
457
  r("WEB_SEARCH_API_KEY", "orchestration", "secret", "web search 后端 API 凭据(brave/tavily)", { danger: [...SEC, ...BILL] }),
456
- r("WEB_SEARCH_ENDPOINT", "orchestration", "url", "web search 后端 endpoint 覆写(searxng 实例地址;brave 下可指向 Brave 兼容网关的完整搜索 URL,如 Nólë;空 = 未设;不是 scheme://host 形的绝对 URL ⇒ 拒启)"),
458
+ r("WEB_SEARCH_ENDPOINT", "orchestration", "url", "web search 后端 endpoint 覆写(searxng 实例地址;brave 下可指向 Brave 兼容网关的完整搜索 URL,如 Nólë;空 = 未设;不是 scheme://host 形的绝对 URL、或带 userinfo(user[:password]@host —— 运行时 fetch 本就拒这一形)⇒ 拒启)"),
457
459
  r("WEB_SEARCH_MAX_RESULTS", "orchestration", "number", "web search 单次结果数上限(非数 / < 1 ⇒ 拒启;> 20 夹到 20)", { derivedDefaultNote: "缺席/空 ⇒ backend 默认 10" }),
458
460
  r("WEB_SEARCH_PROBE_ON_BOOT", "orchestration", "boolean", "boot 期 web search 连通性探测(只认 true/1;含糊值当没开——猜错方向是凭空出网)", { staticDefault: "false" }),
459
461
  r("WEB_SEARCH_PROVIDER", "orchestration", "enum", "web search 工具后端(设合法词=装配 WebSearch;其它/缺席=不装配)", { enumValues: WEB_SEARCH_PROVIDERS, danger: SEC, resolve: (_c, env) => webSearchProviderFromEnv(env) ?? null }),
@@ -67,6 +67,31 @@ export type ApplyEffectiveOpts = ApplyLeg & {
67
67
  }) => void;
68
68
  };
69
69
  export declare function applyEffective(config: ServiceConfig, eff: EffectiveConfig, logger: Logger | undefined, opts: ApplyEffectiveOpts): boolean;
70
+ /**
71
+ * 🔴 **模型面组由哪些 effective 域组成 —— 唯一定义**(7.101.0 验修轮 V1 / codex V-1;OA 验修轮 V2 的以点改面)。
72
+ *
73
+ * collab 在组里:模板带模型引用,新模板配旧目录(或旧模板配新目录)就是一次混代发布。此前「模型面」散在三处各写一份 ——
74
+ * 世代账的键表只列 models 域的落点、stage 的 collab 闸按「模型面没被拒」ad hoc 门住、boot 按组回落 LKG 的合成只换
75
+ * `models` 域 —— 最后那份漏了 collab,于是 boot 回落时 LKG 的目录配上了被拒候选的模板(与 refresh 腿同态不同答)。
76
+ * 读者一律从这里派生,禁手抄:
77
+ * · stage:{@link stageModelPlane} 产出组内**每个**域的产物({@link ModelPlaneStages} 按本表映射),组被拒 ⇒ 一个域都没有;
78
+ * · 世代账:整组拒的 `keys` 与落地的 `applied` 都由 {@link MODEL_PLANE_SURFACES} 按本表派生({@link modelPlaneGroupReport});
79
+ * · boot 按组回落 LKG 的合成文档与 restart 基线:{@link withModelPlaneOf}(`boot/config-center.ts`)。
80
+ * (与 `config.ts` 的配置组视图 `MODEL_PLANE_GROUP_KEYS` 不是一回事:那是 `ServiceConfig` 的分节视图;这里是「中心热应用的
81
+ * 模型面这一组包含哪些 effective 域」。)
82
+ */
83
+ export declare const MODEL_PLANE_DOMAINS: readonly ["models", "collab"];
84
+ export type ModelPlaneDomain = (typeof MODEL_PLANE_DOMAINS)[number];
85
+ /** 模型面组在 effective 上的那一切片(组内各域;`models` 必在、`collab` 可缺席 —— 与 `EffectiveConfig` 同形)。 */
86
+ export type ModelPlaneOf = Pick<EffectiveConfig, ModelPlaneDomain>;
87
+ /**
88
+ * `target` 换上 `source` 的**整个模型面**(组内每个域都取 `source` 的;`source` 缺席的域在结果里同样缺席,不留 `target` 的),
89
+ * 其余域原样是 `target` 的。boot 按组回落 LKG 的两处(回落 apply 的合成文档、restart 对照基线)都走这一只 —— 禁在调用点
90
+ * 手写 `{ ...x, models, collab }`(漏一个域正是 V1 的病形)。纯数据:不改两个入参。
91
+ */
92
+ export declare function withModelPlaneOf<E extends EffectiveConfig>(target: E, source: ModelPlaneOf): E;
93
+ /** 「没有中心模型面」—— 组拒且无 LKG 时进程真正服务的那一代(env 表:没有目录、没有中心 collab 模板)。纯数据,每次新铸。 */
94
+ export declare function buildEnvOnlyModelPlane(): ModelPlaneOf;
70
95
  /**
71
96
  * UNIFORM presence semantics across the center gates (center `f95438a` adopted this from our sentinel flag —
72
97
  * SERVICE-INTEGRATION「Runtime 治理/限额闸」): every gate field is center `.optional()`, so