@sema-agent/server 7.124.1 → 7.125.1

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 (152) hide show
  1. package/MIGRATION.md +138 -2
  2. package/USAGE.md +46 -8
  3. package/dist/audit.d.ts +27 -5
  4. package/dist/audit.js +42 -8
  5. package/dist/boot/execution-env.d.ts +33 -1
  6. package/dist/boot/execution-env.js +31 -2
  7. package/dist/boot/resolve-spec.js +4 -3
  8. package/dist/boot/session-faces.d.ts +14 -1
  9. package/dist/boot/session-faces.js +6 -5
  10. package/dist/boot/stage-02-web-admin-pull.js +5 -1
  11. package/dist/boot/stage-05-execution-env.js +6 -2
  12. package/dist/boot/stage-09-leader.d.ts +1 -1
  13. package/dist/boot/stage-10-http-server.d.ts +1 -1
  14. package/dist/boot/web-admin.js +130 -140
  15. package/dist/capabilities/center-wiring.d.ts +2 -1
  16. package/dist/capabilities/composite-members.d.ts +1 -0
  17. package/dist/capabilities/composite-members.js +1 -0
  18. package/dist/capability-facts.d.ts +28 -3
  19. package/dist/capability-facts.js +20 -0
  20. package/dist/config-catalog.js +4 -1
  21. package/dist/config-d-migration.js +2 -1
  22. package/dist/config-lkg.d.ts +4 -1
  23. package/dist/config-lkg.js +16 -12
  24. package/dist/config-provider.d.ts +4 -1
  25. package/dist/config-provider.js +1 -0
  26. package/dist/config-types.d.ts +10 -3
  27. package/dist/config.js +44 -14
  28. package/dist/env-facts.d.ts +2 -2
  29. package/dist/env-facts.js +3 -2
  30. package/dist/fleet/fleet-bus.d.ts +24 -12
  31. package/dist/fleet/fleet-bus.js +4 -1
  32. package/dist/fleet/workspace-snapshot-residual.d.ts +10 -0
  33. package/dist/fleet/workspace-snapshot-residual.js +35 -0
  34. package/dist/hooks/hook-runner.d.ts +15 -27
  35. package/dist/hooks/hook-runner.js +19 -59
  36. package/dist/http/active-run-conflict.d.ts +3 -0
  37. package/dist/http/active-run-conflict.js +3 -0
  38. package/dist/http/admission.js +7 -7
  39. package/dist/http/approval-sweeps.js +2 -4
  40. package/dist/http/device-placement.js +1 -0
  41. package/dist/http/dispatch.js +1 -1
  42. package/dist/http/durable-run-leg.d.ts +20 -6
  43. package/dist/http/durable-run-leg.js +4 -4
  44. package/dist/http/idempotency.d.ts +83 -38
  45. package/dist/http/idempotency.js +38 -13
  46. package/dist/http/request-guards.d.ts +18 -0
  47. package/dist/http/request-guards.js +12 -3
  48. package/dist/http/resume-legs.js +7 -1
  49. package/dist/http/route-ctx.d.ts +3 -3
  50. package/dist/http/route-ctx.js +1 -1
  51. package/dist/http/routes/a2a-serve.js +27 -11
  52. package/dist/http/routes/capabilities.js +1 -0
  53. package/dist/http/routes/fleet.js +2 -0
  54. package/dist/http/routes/images.js +2 -2
  55. package/dist/http/routes/leader.d.ts +4 -0
  56. package/dist/http/routes/leader.js +13 -3
  57. package/dist/http/routes/observability.js +2 -0
  58. package/dist/http/routes/runs.d.ts +6 -2
  59. package/dist/http/routes/runs.js +71 -101
  60. package/dist/http/routes/session-sync.d.ts +3 -0
  61. package/dist/http/routes/session-sync.js +30 -1
  62. package/dist/http/routes/sessions-list.d.ts +11 -0
  63. package/dist/http/routes/sessions-list.js +6 -6
  64. package/dist/http/routes/sessions.d.ts +6 -1
  65. package/dist/http/routes/sessions.js +41 -10
  66. package/dist/http/routes/tasks.js +63 -21
  67. package/dist/http/run-read-face.d.ts +26 -0
  68. package/dist/http/run-read-face.js +67 -0
  69. package/dist/http/server.d.ts +4 -4
  70. package/dist/http/server.js +1 -2
  71. package/dist/http/submit-replay.d.ts +40 -0
  72. package/dist/http/submit-replay.js +22 -0
  73. package/dist/leader/endpoint.d.ts +10 -5
  74. package/dist/leader/endpoint.js +27 -5
  75. package/dist/observability/cost-quota.d.ts +51 -7
  76. package/dist/observability/cost-quota.js +35 -17
  77. package/dist/observability/fail-open.d.ts +1 -1
  78. package/dist/observability/fail-open.js +1 -1
  79. package/dist/observability/run-terminal.d.ts +5 -9
  80. package/dist/observability/run-terminal.js +2 -7
  81. package/dist/plugins/approval-ask-store-sql.js +1 -1
  82. package/dist/plugins/caching-session-store.d.ts +7 -2
  83. package/dist/plugins/caching-session-store.js +6 -0
  84. package/dist/plugins/device-store-sql.js +1 -1
  85. package/dist/plugins/ensure-index.js +22 -0
  86. package/dist/plugins/file-run-store.d.ts +24 -14
  87. package/dist/plugins/file-run-store.js +52 -12
  88. package/dist/plugins/leader-run-store-sql.d.ts +1 -11
  89. package/dist/plugins/leader-run-store-sql.js +1 -5
  90. package/dist/plugins/local-session-store.d.ts +35 -6
  91. package/dist/plugins/local-session-store.js +87 -18
  92. package/dist/plugins/memory-run-store.d.ts +13 -12
  93. package/dist/plugins/memory-run-store.js +25 -0
  94. package/dist/plugins/one-time-migrations.js +1 -1
  95. package/dist/plugins/pg-cost-quota.d.ts +1 -1
  96. package/dist/plugins/pg-cost-quota.js +2 -5
  97. package/dist/plugins/pg-pool.js +3 -0
  98. package/dist/plugins/pg-session-storage.d.ts +11 -1
  99. package/dist/plugins/pg-session-storage.js +44 -8
  100. package/dist/plugins/remote-env-k8s.d.ts +127 -12
  101. package/dist/plugins/remote-env-k8s.js +270 -27
  102. package/dist/plugins/run-store-sql.d.ts +26 -12
  103. package/dist/plugins/run-store-sql.js +50 -25
  104. package/dist/plugins/schema-consistency.d.ts +1 -13
  105. package/dist/plugins/schema-consistency.js +1 -41
  106. package/dist/plugins/schema-indexes.js +1 -0
  107. package/dist/plugins/schema-live-columns.d.ts +20 -0
  108. package/dist/plugins/schema-live-columns.js +42 -0
  109. package/dist/plugins/store-contracts.d.ts +40 -2
  110. package/dist/plugins/tidb-cost-quota.d.ts +1 -1
  111. package/dist/plugins/tidb-cost-quota.js +2 -5
  112. package/dist/plugins/tidb-pool.js +14 -3
  113. package/dist/plugins/tidb-session-store.d.ts +22 -2
  114. package/dist/plugins/tidb-session-store.js +33 -6
  115. package/dist/plugins/write-behind-counter.d.ts +12 -5
  116. package/dist/plugins/write-behind-counter.js +14 -7
  117. package/dist/prompts-domain-validate.js +3 -2
  118. package/dist/rules-consent.d.ts +2 -2
  119. package/dist/rules-consent.js +2 -2
  120. package/dist/run-local.js +31 -23
  121. package/dist/runs.d.ts +9 -0
  122. package/dist/runs.js +8 -0
  123. package/dist/runtime-governance.d.ts +3 -3
  124. package/dist/runtime-governance.js +2 -4
  125. package/dist/security.d.ts +19 -2
  126. package/dist/session-row.d.ts +53 -0
  127. package/dist/session-row.js +47 -0
  128. package/dist/session-sync.d.ts +6 -0
  129. package/dist/session-sync.js +5 -1
  130. package/dist/session-titler.d.ts +9 -5
  131. package/dist/session-titler.js +3 -1
  132. package/dist/spec-fields.js +2 -2
  133. package/dist/task-cwd.d.ts +0 -20
  134. package/dist/task-cwd.js +2 -10
  135. package/dist/tool-approval.d.ts +5 -7
  136. package/dist/trace/redact.d.ts +23 -50
  137. package/dist/trace/redact.js +42 -9
  138. package/dist/trace/self-minted-wire.d.ts +25 -0
  139. package/dist/trace/self-minted-wire.js +9 -0
  140. package/dist/web-admin-client/apply-effective.d.ts +10 -12
  141. package/dist/web-admin-client/apply-effective.js +2 -3
  142. package/dist/web-admin-client/candidate-gate.d.ts +69 -0
  143. package/dist/web-admin-client/candidate-gate.js +27 -0
  144. package/dist/web-admin-client/http-client.d.ts +2 -1
  145. package/dist/web-admin-client/http-client.js +2 -1
  146. package/dist/web-admin-client/pull-state.d.ts +6 -4
  147. package/dist/web-admin-client/read-warnings.d.ts +29 -5
  148. package/dist/web-admin-client/read-warnings.js +24 -4
  149. package/dist/web-admin-client/stage-limits.js +4 -1
  150. package/dist/workspace-space.d.ts +41 -0
  151. package/dist/workspace-space.js +5 -0
  152. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { buildSessionAudit, buildSessionAuditLocal, buildSessionAuditPg } from "../audit.js";
1
+ import { buildRewindHandles, buildSessionAudit, buildSessionAuditLocal, buildSessionAuditPg } from "../audit.js";
2
2
  import { makeDegenerateInstrument } from "../degenerate-instrument.js";
3
3
  import { PlanCacheProbe } from "../plan-cache-probe.js";
4
4
  import { purgeScratchpadDir } from "../env-facts.js";
@@ -14,12 +14,13 @@ export function createSessionFaces(ctx) {
14
14
  const auditLocalRoot = backend?.kind === "local" ? (config.localDataRoot ?? localRoot) : undefined;
15
15
  const ownerAware = sessionStore;
16
16
  const sessionAudit = mysqlPool || auditPgPool || auditLocalRoot
17
- ? async (sessionId) => {
17
+ ? async (sessionId, opts) => {
18
+ const handles = opts !== undefined && resumeAnchorStore !== undefined ? buildRewindHandles(await resumeAnchorStore.listBySession(sessionId), opts.reader) : undefined;
18
19
  const audit = mysqlPool
19
- ? await buildSessionAudit(mysqlPool, sessionId)
20
+ ? await buildSessionAudit(mysqlPool, sessionId, handles)
20
21
  : auditPgPool
21
- ? await buildSessionAuditPg(auditPgPool, sessionId)
22
- : await buildSessionAuditLocal(auditLocalRoot, sessionId);
22
+ ? await buildSessionAuditPg(auditPgPool, sessionId, handles)
23
+ : await buildSessionAuditLocal(auditLocalRoot, sessionId, handles);
23
24
  if (!audit)
24
25
  return undefined;
25
26
  return { ...audit, owner: (await ownerAware.ownerOf?.(sessionId)) ?? null };
@@ -2,10 +2,14 @@ import { createUnpricedCeilingVerdict } from "./unpriced-ceiling-verdict.js";
2
2
  import { createWebAdminRuntime } from "./web-admin.js";
3
3
  import { composeExecutionLane, FILE_SOURCED_LANES } from "./execution-lane.js";
4
4
  import { serviceConfigSourceKind } from "../config-provider.js";
5
+ import { logConfigDirMigration, migrateLocalConfigDirectory } from "../config-d-migration.js";
5
6
  import { defaultModelRefusal, refusedNameMintedRefusal } from "../model-entry-refusal.js";
6
7
  export async function runStage02(ctx) {
7
8
  const { config, localRoot, logger, metrics } = ctx;
8
- await composeExecutionLane(config, { configSource: serviceConfigSourceKind(config), localRoot, logger, drivesFromFile: FILE_SOURCED_LANES });
9
+ const configSource = serviceConfigSourceKind(config);
10
+ if (configSource === "local")
11
+ logConfigDirMigration(logger, await migrateLocalConfigDirectory(localRoot));
12
+ await composeExecutionLane(config, { configSource, localRoot, logger, drivesFromFile: FILE_SOURCED_LANES });
9
13
  const webAdmin = await createWebAdminRuntime({ config, logger, metrics, localRoot });
10
14
  const noDefaultModel = defaultModelRefusal(config, "env");
11
15
  if (noDefaultModel !== undefined) {
@@ -1,12 +1,16 @@
1
1
  import { createToolTracer, createPermissionDeniedMeter } from "../observability/tool-trace.js";
2
2
  import { composeHooks } from "../hooks/hook-runner.js";
3
3
  import { assertDeviceLaneWired, createDeviceLane } from "./device-lane.js";
4
- import { createExecutionEnv } from "./execution-env.js";
4
+ import { assertWorkspaceSpaceReadable, createExecutionEnv } from "./execution-env.js";
5
+ import { sessionFaceReaderOf } from "./session-incarnation-seat.js";
6
+ import * as facts from "../capability-facts.js";
7
+ import { sessionStoreLabelOf } from "../store-posture.js";
5
8
  export function runStage05(ctx) {
6
- const { backend, config, instanceId, logger, metrics, taskAttachmentStore } = ctx;
9
+ const { backend, config, instanceId, logger, metrics, sessionStore, taskAttachmentStore } = ctx;
7
10
  const deviceLane = config.remoteExec?.provider === "device" ? createDeviceLane({ config, logger, metrics, backend, replicaId: instanceId }) : undefined;
8
11
  const { perTaskImage, sessionEnvSelection, perSessionCwd, setSessionCwd, setSessionShellEnv, setSessionInheritEnv, fenceSessionOwner, dropSessionScoped, noteSessionIncarnation, sessionIncarnationOf, executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager, } = createExecutionEnv({ config, logger, metrics, taskAttachmentStore, ...(deviceLane ? { deviceLane } : {}) });
9
12
  assertDeviceLaneWired(config.remoteExec?.provider === "device", deviceLane, executionEnvFactory !== undefined);
13
+ assertWorkspaceSpaceReadable(facts.workspaceSpace(config), sessionFaceReaderOf(sessionStore), sessionStoreLabelOf(config.sessionBackend, backend?.kind));
10
14
  const toolTracer = config.toolTrace ? createToolTracer(logger) : undefined;
11
15
  if (toolTracer)
12
16
  logger.info("tool_trace_enabled", {});
@@ -17,7 +17,7 @@ export declare function runStage09(ctx: Awaited<ReturnType<typeof runStage08>>):
17
17
  childrenAlive: readonly string[];
18
18
  }>) | undefined;
19
19
  webAdminJwtVerifier: import("../auth-bridge.js").WebAdminJwtVerifier | undefined;
20
- sessionAudit: ((sessionId: string) => Promise<{
20
+ sessionAudit: ((sessionId: string, opts?: import("../audit.js").SessionAuditReadOpts) => Promise<{
21
21
  owner: string | null;
22
22
  sessionId: string;
23
23
  createdAt: string;
@@ -16,7 +16,7 @@ export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>):
16
16
  childrenAlive: readonly string[];
17
17
  }>) | undefined;
18
18
  webAdminJwtVerifier: import("../auth-bridge.js").WebAdminJwtVerifier | undefined;
19
- sessionAudit: ((sessionId: string) => Promise<{
19
+ sessionAudit: ((sessionId: string, opts?: import("../audit.js").SessionAuditReadOpts) => Promise<{
20
20
  owner: string | null;
21
21
  sessionId: string;
22
22
  createdAt: string;
@@ -9,20 +9,21 @@ import { CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore
9
9
  import { buildPricing } from "../budget.js";
10
10
  import { applyCenterPlugins } from "../capabilities/center-plugins.js";
11
11
  import { applyCatalogToSource, withPromptArtifactBackfill } from "../capabilities/center-prompts.js";
12
- import { validatePromptsDomain, CORE_ENGINE_VERSION } from "../prompts-domain-validate.js";
12
+ import { CORE_ENGINE_VERSION } from "../prompts-domain-validate.js";
13
13
  import { centerScenarios, centerScenarioDetails } from "../capabilities/scenarios.js";
14
14
  import { isRosterGateArmed } from "../config.js";
15
15
  import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg, legacyV1LkgPath, migrateV1Lkg } from "../config-lkg.js";
16
16
  import { answerStillCurrent, centerStateBin, credentialIdentityNow, judgeCenterCredentialAtBoot } from "../center-credential.js";
17
17
  import { buildCenterCredentialHealthView, createCenterCredentialRenewer } from "../center-credential-renewer.js";
18
18
  import { centerManagedConfigKeys } from "../web-admin-client/apply-effective.js";
19
- import { logConfigDirMigration, migrateLocalConfigDirectory } from "../config-d-migration.js";
20
19
  import { createConfigProvider, raceBootFetch, serviceConfigSourceKind, BOOT_FETCH_DEFERRED } from "../config-provider.js";
21
20
  import { createKeyResolver } from "../key-resolver.js";
22
21
  import { buildSealedKeyPublicFace, ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
23
22
  import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, withModelPlaneOf, fullyLandedForLkg, fetchPromptArtifact } from "../web-admin-client/facade.js";
24
23
  import { buildConfigHandoff, buildRestartSignal, configHandoffMissing } from "../fleet/engine-signal.js";
25
24
  import { createMcpRevocationLedger, enabledMcpEntries } from "../web-admin-client/mcp-revocation.js";
25
+ import { buildUnreadConfigFileFields } from "../web-admin-client/read-warnings.js";
26
+ import { CONFIG_DOMAIN_INVALID_NOTE, buildCandidateRejectedFields, judgeConfigCandidate } from "../web-admin-client/candidate-gate.js";
26
27
  import { SHA256_HEX_RE } from "../digest-form.js";
27
28
  import { BOOT_REJUDGE_OF_REFUSED_GROUP, createConfigApplyLedger, modelPlaneCommitted, refusedGroups } from "../web-admin-client/apply-ledger.js";
28
29
  function lkgReadNote(scope) {
@@ -127,11 +128,12 @@ export async function createWebAdminRuntime(ctx) {
127
128
  localDir: localRoot,
128
129
  })
129
130
  : undefined;
130
- if (configProvider?.kind === "local")
131
- logConfigDirMigration(logger, await migrateLocalConfigDirectory(localRoot));
131
+ const localDirField = configProvider?.kind === "local" ? { dir: configProvider.root } : {};
132
132
  const rosterGateArmed = isRosterGateArmed(cc);
133
133
  const modelReadyState = { ready: !rosterGateArmed };
134
- const warnDroppedEntries = (r, phase) => {
134
+ const warnReadNotices = (r, phase) => {
135
+ for (const f of r.unreadFiles ?? [])
136
+ logger.warn("config_unread_file", buildUnreadConfigFileFields(f, localDirField.dir));
135
137
  for (const d of r.droppedEntries ?? []) {
136
138
  logger.warn("config_entry_dropped", {
137
139
  domain: d.domain,
@@ -144,6 +146,13 @@ export async function createWebAdminRuntime(ctx) {
144
146
  });
145
147
  }
146
148
  };
149
+ const announceCandidateRejected = (r, verdict, leg) => {
150
+ const domains = verdict.domains.join(",");
151
+ if (lastRejectedCandidate?.version === r.effective.version && lastRejectedCandidate.domains === domains)
152
+ return;
153
+ lastRejectedCandidate = { version: r.effective.version, domains, at: Date.now() };
154
+ logger.warn("config_candidate_rejected", buildCandidateRejectedFields(r, verdict, leg, { lkgVersion: latestEffective?.version ?? "(env)", ...localDirField }));
155
+ };
147
156
  const selfReportListeners = [];
148
157
  const notifySelfReport = (changed) => {
149
158
  for (const listener of selfReportListeners) {
@@ -275,13 +284,12 @@ export async function createWebAdminRuntime(ctx) {
275
284
  };
276
285
  if (configProvider && !cc?.dryRun)
277
286
  await rebasePromptState(promptStateIdentity, "boot");
278
- const adoptCenterPrompts = async (eff, phase) => {
287
+ const adoptCenterPrompts = async (eff, prompts, phase) => {
279
288
  const adoptedIdentity = candidateIdentityOf(eff);
280
289
  if (adoptedIdentity !== undefined && adoptedIdentity !== promptStateIdentity) {
281
290
  await rebasePromptState(adoptedIdentity, phase);
282
291
  }
283
- const raw = eff?.prompts;
284
- if (raw === undefined) {
292
+ if (prompts === undefined) {
285
293
  if (centerPrompts)
286
294
  logger.info("center_prompts_cleared", { phase, note: "effective carries no prompts key (publish gate off) — built-in providers resume" });
287
295
  const cleared = await applyCatalogToSource(catalogSource, undefined, centerPrompts, logger, phase);
@@ -294,29 +302,24 @@ export async function createWebAdminRuntime(ctx) {
294
302
  }
295
303
  return;
296
304
  }
297
- const v = validatePromptsDomain(raw);
298
- if (!v.ok) {
299
- logger.warn("center_prompts_invalid", { phase, error: v.error, kept: centerPrompts?.declarations?.packId ?? (centerPrompts?.catalog ? "(catalog)" : "(builtin)"), note: "malformed prompts face rejected (core assemble would THROW per task) — keeping the previous pack/built-ins; fix the center payload" });
300
- return;
301
- }
302
- if (centerPrompts?.identity !== v.value.identity) {
305
+ if (centerPrompts?.identity !== prompts.identity) {
303
306
  logger.info("center_prompts_adopted", {
304
307
  phase,
305
- axes: v.value.axes,
306
- ...(v.value.declarations
307
- ? { packId: v.value.declarations.packId, contentDigest: v.value.declarations.contentDigest, sections: v.value.declarations.sections.length, scenarioOverrides: Object.keys(v.value.declarations.scenarioOverrides ?? {}).length }
308
+ axes: prompts.axes,
309
+ ...(prompts.declarations
310
+ ? { packId: prompts.declarations.packId, contentDigest: prompts.declarations.contentDigest, sections: prompts.declarations.sections.length, scenarioOverrides: Object.keys(prompts.declarations.scenarioOverrides ?? {}).length }
308
311
  : {}),
309
- ...(v.value.catalog ? { artifactDigest: v.value.catalog.artifact.artifactDigest, catalogDigest: v.value.catalog.artifact.payload.catalogDigest, sourceRevision: v.value.catalog.sourceRevision } : {}),
312
+ ...(prompts.catalog ? { artifactDigest: prompts.catalog.artifact.artifactDigest, catalogDigest: prompts.catalog.artifact.payload.catalogDigest, sourceRevision: prompts.catalog.sourceRevision } : {}),
310
313
  note: "new tasks assemble the new pack (epoch re-pins at the next task boundary); in-flight sessions stay pinned",
311
314
  });
312
315
  }
313
- const transitioned = await applyCatalogToSource(catalogSource, v.value, centerPrompts, logger, phase);
316
+ const transitioned = await applyCatalogToSource(catalogSource, prompts, centerPrompts, logger, phase);
314
317
  if (transitioned) {
315
- centerPrompts = v.value;
318
+ centerPrompts = prompts;
316
319
  pendingCatalogFaces = undefined;
317
320
  }
318
321
  else {
319
- pendingCatalogFaces = { target: v.value };
322
+ pendingCatalogFaces = { target: prompts };
320
323
  }
321
324
  };
322
325
  let bootConfigPending;
@@ -442,7 +445,7 @@ export async function createWebAdminRuntime(ctx) {
442
445
  const lkgEffective = read.lkg.effective;
443
446
  candidateIdentity.set(lkgEffective, identity);
444
447
  bindValidator(read.lkg.etag, identity);
445
- return { effective: lkgEffective, ...(read.lkg.etag !== undefined ? { etag: read.lkg.etag } : {}) };
448
+ return { effective: lkgEffective, ...(read.lkg.etag !== undefined ? { etag: read.lkg.etag } : {}), prompts: read.prompts };
446
449
  };
447
450
  const bootApplyLkgInline = async (lkgR) => {
448
451
  const lkgGen = generationOf(lkgR.effective.version);
@@ -461,7 +464,7 @@ export async function createWebAdminRuntime(ctx) {
461
464
  if (lkgSurvivesRestart && lkgIdentity !== undefined)
462
465
  noteHandoff({ durable: { version: lkgR.effective.version, identity: lkgIdentity } });
463
466
  markApplied(lkgGen, lkgR.effective.version);
464
- await adoptCenterPrompts(lkgR.effective, "boot-lkg");
467
+ await adoptCenterPrompts(lkgR.effective, lkgR.prompts, "boot-lkg");
465
468
  const lkgPlane = modelPlaneCommitted(lkgReport);
466
469
  if (lkgPlane)
467
470
  markRosterLanded(lkgR.effective);
@@ -476,16 +479,46 @@ export async function createWebAdminRuntime(ctx) {
476
479
  const cfgModels = (lkgR.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
477
480
  logger.info("config_loaded", { source: "lkg", version: lkgR.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels });
478
481
  };
482
+ const bootFallBackWhole = async (why) => {
483
+ const lkgFallback = await bootFromLkg(why, "whole");
484
+ if (!lkgFallback)
485
+ return;
486
+ lkgBooted = true;
487
+ try {
488
+ await bootApplyLkgInline(lkgFallback);
489
+ }
490
+ catch (applyErr) {
491
+ lkgBooted = false;
492
+ logger.warn("config_lkg_apply_failed", { err: String(applyErr), note: "persisted LKG failed to apply — env fallback serves; the cache rewrites on the next clean candidate" });
493
+ }
494
+ };
495
+ const warnLocalNoModels = (r, rejected) => {
496
+ const modelsDomainErr = (r.domainErrors ?? []).find((de) => de.domain === "models");
497
+ const modelsDropped = (r.droppedEntries ?? []).filter((d) => d.domain === "models");
498
+ const totalModels = (r.effective.models?.models ?? []).length;
499
+ logger.warn("config_local_no_models", {
500
+ ...localDirField,
501
+ ...(modelsDomainErr ? { domainError: modelsDomainErr.error } : {}),
502
+ ...(modelsDropped.length > 0 ? { droppedEntries: modelsDropped.map((d) => d.entry) } : {}),
503
+ hint: rejected && modelsDomainErr
504
+ ? "config.d/models.json 在场但 models 域判形失败,整份本地候选被拒(真因见本行 domainError)— 模型继续走 env 兜底"
505
+ : rejected
506
+ ? "config.d 里别的域判形失败,整份本地候选被拒(坏域与真因见 config_candidate_rejected)— config.d/models.json 的模型不用,继续走 env 兜底"
507
+ : modelsDropped.length > 0 && totalModels === 0
508
+ ? "config.d/models.json 的条目全部被逐条丢弃(逐条真因见 config_entry_dropped)— 模型继续走 env 兜底"
509
+ : totalModels > 0
510
+ ? `config.d/models.json 有 ${totalModels} 条模型但全部 enabled:false — 模型继续走 env 兜底`
511
+ : "config.d/models.json 空或 CONFIG_LOCAL_DIR 指错目录(选目录旋钮是 CONFIG_LOCAL_DIR,不是 LOCAL_DATA_ROOT)— 模型继续走 env 兜底",
512
+ });
513
+ };
479
514
  let bootApplyPhase = false;
480
515
  try {
481
516
  const bootFetch = pullEffective(configProvider, undefined);
482
517
  const raced = await raceBootFetch(bootFetch, config.configBootFetchBudgetMs);
483
- let lkgFallback = null;
484
518
  if (raced === BOOT_FETCH_DEFERRED) {
485
519
  bootConfigPending = bootFetch;
486
520
  bootFetch.catch(() => { });
487
- lkgFallback = await bootFromLkg("boot fetch deferred (budget exceeded)", "whole");
488
- lkgBooted = lkgFallback !== null;
521
+ await bootFallBackWhole("boot fetch deferred (budget exceeded)");
489
522
  logger.warn("config_boot_fetch_deferred", {
490
523
  source: configProvider.kind,
491
524
  budgetMs: config.configBootFetchBudgetMs,
@@ -495,84 +528,76 @@ export async function createWebAdminRuntime(ctx) {
495
528
  : "center slow/unreachable and no usable LKG — serving on the env fallback; the pull continues in the background (late-boot adoption on arrival). Raise CONFIG_BOOT_FETCH_BUDGET_MS to block boot instead.",
496
529
  });
497
530
  }
498
- if (raced === BOOT_FETCH_DEFERRED && lkgFallback) {
499
- try {
500
- await bootApplyLkgInline(lkgFallback);
501
- }
502
- catch (applyErr) {
503
- lkgBooted = false;
504
- logger.warn("config_lkg_apply_failed", { err: String(applyErr), note: "persisted LKG failed to apply — env fallback serves; the cache rewrites on the next clean candidate" });
505
- }
506
- }
507
531
  const r = raced === BOOT_FETCH_DEFERRED ? null : raced;
508
532
  if (r) {
509
533
  bootApplyPhase = true;
510
534
  for (const de of r.domainErrors ?? [])
511
- 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)" });
512
- warnDroppedEntries(r, "boot");
535
+ logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: CONFIG_DOMAIN_INVALID_NOTE });
513
536
  if (dryRun) {
514
- logEffectiveDiff(config, r.effective, logger);
515
- if (fullyLandedForLkg(r, undefined))
537
+ const dryVerdict = judgeConfigCandidate(r);
538
+ if (dryVerdict.verdict === "rejected") {
539
+ announceCandidateRejected(r, dryVerdict, "boot");
540
+ }
541
+ else {
542
+ warnReadNotices(r, "boot");
543
+ logEffectiveDiff(config, r.effective, logger);
516
544
  ccEtag = r.etag;
545
+ }
517
546
  }
518
547
  else {
519
- const bootGen = generationOf(r.effective.version);
520
- applyLedger.seenTarget(bootGen, r.effective.version);
521
- let bootReport;
522
- applyEffective(config, r.effective, logger, {
523
- leg: "boot",
524
- sealedKeys,
525
- generation: bootGen,
526
- report: (rep) => {
527
- bootReport = rep;
528
- recordApply(rep);
529
- },
530
- });
531
- markApplied(bootGen, r.effective.version);
532
- const bootClean = fullyLandedForLkg(r, bootReport);
533
- if (bootClean)
534
- ccEtag = r.etag;
535
- await adoptCenterPrompts(r.effective, "boot");
536
- let bootPlaneEff = modelPlaneCommitted(bootReport) ? r.effective : undefined;
537
- if (bootPlaneEff === undefined) {
538
- const lkgPlane = await bootFromLkg("boot candidate's model plane was refused", "model-plane");
539
- if (lkgPlane) {
540
- let planeReport;
541
- applyEffective(config, withModelPlaneOf(r.effective, lkgPlane.effective), logger, { leg: "boot", sealedKeys, generation: bootGen, report: (rep) => (planeReport = rep) });
542
- if (modelPlaneCommitted(planeReport)) {
543
- bootPlaneEff = lkgPlane.effective;
544
- 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" });
545
- }
546
- }
548
+ const bootVerdict = judgeConfigCandidate(r);
549
+ if (bootVerdict.verdict === "rejected") {
550
+ await bootFallBackWhole(`boot candidate rejected: ${bootVerdict.domains.join(",")}`);
551
+ const rejectedGen = generationOf(r.effective.version);
552
+ applyLedger.seenTarget(rejectedGen, r.effective.version);
553
+ announceCandidateRejected(r, bootVerdict, "boot");
554
+ if (configProvider.kind === "local")
555
+ warnLocalNoModels(r, true);
547
556
  }
548
- if (bootPlaneEff !== undefined)
549
- markRosterLanded(bootPlaneEff);
550
- effective = r.effective;
551
- if (bootPlaneEff !== undefined)
552
- appliedPlaneEff = bootPlaneEff;
553
- if (bootClean)
554
- latestEffective = r.effective;
555
- if (bootClean && !lkgBooted)
556
- bootLkgCandidate = { effective: r.effective, etag: r.etag };
557
- const cfgModels = (r.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
558
- const cfgDir = configProvider.kind === "local" ? { dir: configProvider.root } : {};
559
- logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels, ...cfgDir });
560
- if (configProvider.kind === "local" && cfgModels === 0) {
561
- const modelsDomainErr = (r.domainErrors ?? []).find((de) => de.domain === "models");
562
- const modelsDropped = (r.droppedEntries ?? []).filter((d) => d.domain === "models");
563
- const totalModels = (r.effective.models?.models ?? []).length;
564
- logger.warn("config_local_no_models", {
565
- ...cfgDir,
566
- ...(modelsDomainErr ? { domainError: modelsDomainErr.error } : {}),
567
- ...(modelsDropped.length > 0 ? { droppedEntries: modelsDropped.map((d) => d.entry) } : {}),
568
- hint: modelsDomainErr
569
- ? "config.d/models.json 在场但 models 域判形失败落 default(真因见本行 domainError)— 模型继续走 env 兜底"
570
- : modelsDropped.length > 0 && totalModels === 0
571
- ? "config.d/models.json 的条目全部被逐条丢弃(逐条真因见 config_entry_dropped)— 模型继续走 env 兜底"
572
- : totalModels > 0
573
- ? `config.d/models.json 有 ${totalModels} 条模型但全部 enabled:false — 模型继续走 env 兜底`
574
- : "config.d/models.json 空或 CONFIG_LOCAL_DIR 指错目录(选目录旋钮是 CONFIG_LOCAL_DIR,不是 LOCAL_DATA_ROOT)— 模型继续走 env 兜底",
557
+ else {
558
+ warnReadNotices(r, "boot");
559
+ const bootGen = generationOf(r.effective.version);
560
+ applyLedger.seenTarget(bootGen, r.effective.version);
561
+ let bootReport;
562
+ applyEffective(config, r.effective, logger, {
563
+ leg: "boot",
564
+ sealedKeys,
565
+ generation: bootGen,
566
+ report: (rep) => {
567
+ bootReport = rep;
568
+ recordApply(rep);
569
+ },
575
570
  });
571
+ markApplied(bootGen, r.effective.version);
572
+ const bootClean = fullyLandedForLkg(r, bootReport);
573
+ if (bootClean)
574
+ ccEtag = r.etag;
575
+ await adoptCenterPrompts(r.effective, bootVerdict.prompts, "boot");
576
+ let bootPlaneEff = modelPlaneCommitted(bootReport) ? r.effective : undefined;
577
+ if (bootPlaneEff === undefined) {
578
+ const lkgPlane = await bootFromLkg("boot candidate's model plane was refused", "model-plane");
579
+ if (lkgPlane) {
580
+ let planeReport;
581
+ applyEffective(config, withModelPlaneOf(r.effective, lkgPlane.effective), logger, { leg: "boot", sealedKeys, generation: bootGen, report: (rep) => (planeReport = rep) });
582
+ if (modelPlaneCommitted(planeReport)) {
583
+ bootPlaneEff = lkgPlane.effective;
584
+ 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" });
585
+ }
586
+ }
587
+ }
588
+ if (bootPlaneEff !== undefined)
589
+ markRosterLanded(bootPlaneEff);
590
+ effective = r.effective;
591
+ if (bootPlaneEff !== undefined)
592
+ appliedPlaneEff = bootPlaneEff;
593
+ if (bootClean)
594
+ latestEffective = r.effective;
595
+ if (bootClean && !lkgBooted)
596
+ bootLkgCandidate = { effective: r.effective, etag: r.etag };
597
+ const cfgModels = (r.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
598
+ logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels, ...localDirField });
599
+ if (configProvider.kind === "local" && cfgModels === 0)
600
+ warnLocalNoModels(r, false);
576
601
  }
577
602
  }
578
603
  if (configProvider.kind === "remote")
@@ -584,17 +609,7 @@ export async function createWebAdminRuntime(ctx) {
584
609
  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" });
585
610
  else
586
611
  logger.warn("config_source_unreachable_using_env", { source: configProvider.kind, err: String(err) });
587
- const lkgFallback = await bootFromLkg(`${bootApplyPhase ? "boot apply refused" : "boot fetch failed"}: ${String(err).slice(0, 120)}`, "whole");
588
- if (lkgFallback) {
589
- lkgBooted = true;
590
- try {
591
- await bootApplyLkgInline(lkgFallback);
592
- }
593
- catch (applyErr) {
594
- lkgBooted = false;
595
- logger.warn("config_lkg_apply_failed", { err: String(applyErr), note: "persisted LKG failed to apply — env fallback serves" });
596
- }
597
- }
612
+ await bootFallBackWhole(`${bootApplyPhase ? "boot apply refused" : "boot fetch failed"}: ${String(err).slice(0, 120)}`);
598
613
  }
599
614
  if (!dryRun && cc !== undefined && currentIdentity() !== promptStateIdentity)
600
615
  await rebasePromptState(currentIdentity(), "boot-end");
@@ -841,32 +856,13 @@ export async function createWebAdminRuntime(ctx) {
841
856
  if (r) {
842
857
  const tickGen = arrival !== undefined ? useArrivalGeneration(r.effective.version, arrival.ordinal) : generationOf(r.effective.version);
843
858
  applyLedger.seenTarget(tickGen, r.effective.version);
844
- const promptsRaw = r.effective.prompts;
845
- const promptsGate = promptsRaw !== undefined ? validatePromptsDomain(promptsRaw) : { ok: true };
846
- const badDomains = [
847
- ...new Set([...(r.domainErrors ?? []).map((de) => de.domain), ...(promptsGate.ok ? [] : ["prompts"])]),
848
- ]
849
- .sort()
850
- .join(",");
851
- if (badDomains) {
852
- if (lastRejectedCandidate?.version !== r.effective.version || lastRejectedCandidate.domains !== badDomains) {
853
- lastRejectedCandidate = { version: r.effective.version, domains: badDomains, at: Date.now() };
854
- logger.warn("config_candidate_rejected", {
855
- version: r.effective.version,
856
- domains: badDomains,
857
- errors: [
858
- ...(r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })),
859
- ...(promptsGate.ok ? [] : [{ domain: "prompts", error: promptsGate.error }]),
860
- ],
861
- ...(r.droppedEntries !== undefined ? { droppedEntries: r.droppedEntries } : {}),
862
- lkgVersion: latestEffective?.version ?? "(env)",
863
- note: "candidate rejected whole (§9.5-5) — live config keeps the last-known-good; fix the named files to unblock",
864
- });
865
- }
859
+ const verdict = judgeConfigCandidate(r);
860
+ if (verdict.verdict === "rejected") {
861
+ announceCandidateRejected(r, verdict, arrival !== undefined ? "late-boot" : "refresh");
866
862
  return "ok";
867
863
  }
868
864
  lastRejectedCandidate = undefined;
869
- warnDroppedEntries(r, "refresh");
865
+ warnReadNotices(r, "refresh");
870
866
  if (ccRef?.dryRun) {
871
867
  logEffectiveDiff(config, r.effective, logger);
872
868
  ccEtag = r.etag;
@@ -898,7 +894,7 @@ export async function createWebAdminRuntime(ctx) {
898
894
  if (tickPlane)
899
895
  appliedPlaneEff = r.effective;
900
896
  planeDeferredNoHandoff = undefined;
901
- await adoptCenterPrompts(r.effective, "refresh");
897
+ await adoptCenterPrompts(r.effective, verdict.prompts, "refresh");
902
898
  if (tickPlane)
903
899
  markRosterLanded(r.effective);
904
900
  if (!fullyLandedForLkg(r, applyReport)) {
@@ -969,18 +965,12 @@ export async function createWebAdminRuntime(ctx) {
969
965
  };
970
966
  const deferredBootApplyInner = async (r, lateGen) => {
971
967
  applyLedger.seenTarget(lateGen, r.effective.version);
972
- const promptsRaw = r.effective.prompts;
973
- const promptsGate = promptsRaw !== undefined ? validatePromptsDomain(promptsRaw) : { ok: true };
974
- if ((r.domainErrors ?? []).length > 0 || !promptsGate.ok) {
975
- logger.warn("config_boot_deferred_candidate_rejected", {
976
- version: r.effective.version,
977
- errors: [...(r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })), ...(promptsGate.ok ? [] : [{ domain: "prompts", error: promptsGate.error }])],
978
- ...(r.droppedEntries !== undefined ? { droppedEntries: r.droppedEntries } : {}),
979
- note: "late boot pull carried an invalid candidate — env fallback keeps serving; the refresh cadence re-judges the same tree (etag not advanced)",
980
- });
968
+ const lateVerdict = judgeConfigCandidate(r);
969
+ if (lateVerdict.verdict === "rejected") {
970
+ announceCandidateRejected(r, lateVerdict, "late-boot");
981
971
  return;
982
972
  }
983
- warnDroppedEntries(r, "boot-deferred");
973
+ warnReadNotices(r, "boot-deferred");
984
974
  if (ccRef?.dryRun) {
985
975
  logEffectiveDiff(config, r.effective, logger);
986
976
  ccEtag = r.etag;
@@ -1011,7 +1001,7 @@ export async function createWebAdminRuntime(ctx) {
1011
1001
  if (latePlane)
1012
1002
  appliedPlaneEff = r.effective;
1013
1003
  planeDeferredNoHandoff = undefined;
1014
- await adoptCenterPrompts(r.effective, "boot-deferred");
1004
+ await adoptCenterPrompts(r.effective, lateVerdict.prompts, "boot-deferred");
1015
1005
  if (latePlane)
1016
1006
  markRosterLanded(r.effective);
1017
1007
  if (!fullyLandedForLkg(r, applyReportLate)) {
@@ -7,7 +7,8 @@
7
7
  * ## 形与 present-iff
8
8
  * 整个键只在接了中心(`config.webAdmin` 在场)时出现;`lease:"absent"`(中心三件不齐 ⇒ 没装租约管理器)时 `leaseHealth` /
9
9
  * `failMode` / `caller` 三键缺席。「键缺席」于是两义:老 worker / 没接中心 —— 同一体的 `version` 能分。
10
- * · `effectivePull` = 最近一次实拉成功(`lastPull === "ok"`;LKG 起服 / 从未成功 / 最近一拍失败 = false);
10
+ * · `effectivePull` = 最近一次实拉中心答了(`lastPull === "ok"`):拉取失败形(拉取失败后 LKG 起服 / 从未成功 / 最近一拍失败)= false;
11
+ * 起服遇拒读回 LKG 且中心答了 = true(304、被候选判官整拒的拍同理);
11
12
  * · `effectiveRevision` = 与 `/health.configAppliedVersion` 同一只 ledger 视图(`web-admin-client/pull-state.ts`);
12
13
  * · `lease` / `leaseHealth` / `failMode` / `caller` = 租约管理器的 `snapshot()` 与生效姿态(与执行的那一只同源);
13
14
  * · `usageReport` = 用量环装没装(装配事实:累加器与环同一只谓词 `centerLaneConfigured`);
@@ -53,6 +53,7 @@ export declare const COMPOSITE_CAPABILITY_MEMBERS: {
53
53
  readonly retention: readonly ["mode", "maxAgeDays"];
54
54
  readonly workflowsGate: readonly ["engineCan", "denial"];
55
55
  readonly sealedKey: readonly ["alg", "publicKeyId", "publicKey"];
56
+ readonly workspaceSync: readonly ["persist", "keep"];
56
57
  };
57
58
  type CompositeTable = typeof COMPOSITE_CAPABILITY_MEMBERS;
58
59
  /** 值的**具名成员对象臂**(标量 / 字面词 / `false` / `null` / `undefined` / 数组 / 函数臂剥掉)。 */
@@ -14,6 +14,7 @@ export const COMPOSITE_CAPABILITY_MEMBERS = {
14
14
  retention: ["mode", "maxAgeDays"],
15
15
  workflowsGate: ["engineCan", "denial"],
16
16
  sealedKey: ["alg", "publicKeyId", "publicKey"],
17
+ workspaceSync: ["persist", "keep"],
17
18
  };
18
19
  export function gateCompositeMembers(body) {
19
20
  return body;
@@ -23,6 +23,7 @@ import type { ServiceConfig } from "./config.js";
23
23
  import type { OwnerAwareSessionStore } from "./security.js";
24
24
  import type { ServiceFileHistoryStore } from "./plugins/store-backend.js";
25
25
  import { resolveStreamApprovalGate } from "./tool-approval.js";
26
+ import { type WorkspaceSyncCapability } from "./workspace-space.js";
26
27
  /** why 闭集:词 → 给运维看的一句义(诊断 / 测试读它;wire 上的拒句由各门自写)。 */
27
28
  export declare const CAPABILITY_FACT_WHY: {
28
29
  readonly run_store_absent: "no durable run store is wired (DB_BACKEND=mysql|pg|local)";
@@ -36,6 +37,7 @@ export declare const CAPABILITY_FACT_WHY: {
36
37
  readonly session_leaf_face_absent: "the session store has no leaf-read face (sessionStorage.getLeafId)";
37
38
  readonly session_lister_absent: "no session enumerator is wired (session store or run store listSessions)";
38
39
  readonly session_fork_face_absent: "the session store has no fork face";
40
+ readonly session_row_faces_absent: "the session store has no session-row faces (sessionFaceOf / sessionSummaryOf / patchSessionMeta)";
39
41
  readonly session_purge_absent: "no session purge coordinator is wired";
40
42
  readonly session_policy_store_absent: "no session policy store is wired";
41
43
  readonly store_backend_absent: "no store backend is wired (DB_BACKEND=memory, multi-tenant bare boot, or half-configured SQL)";
@@ -79,6 +81,8 @@ export declare const CAPABILITY_FACT_WHY: {
79
81
  readonly scheduler_disabled: "the self-wake scheduler is off (SCHEDULER_ENABLED)";
80
82
  readonly multi_tenant_deployment: "this is a multi-tenant deployment (REQUIRE_PRINCIPAL=true)";
81
83
  readonly not_host_lane: "the execution lane is not the single-user host lane (REMOTE_EXEC=host)";
84
+ readonly not_k8s_lane: "the execution lane is not the k8s pod sandbox (REMOTE_EXEC=k8s)";
85
+ readonly workspace_snapshot_store_absent: "no workspace snapshot store is configured (MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY)";
82
86
  };
83
87
  export type CapabilityFactWhy = keyof typeof CAPABILITY_FACT_WHY;
84
88
  /** 不可用臂。 */
@@ -137,6 +141,12 @@ export declare function sessionList(d: Pick<Deps, "sessionStorage" | "runStore">
137
141
  export declare function sessionFork(d: Pick<Deps, "sessionStorage">): Fact<"session_fork_face_absent" | "session_owner_face_absent", {
138
142
  sessionStorage: WithMethods<OwnerAwareSessionStore, "ownerOf" | "fork">;
139
143
  }>;
144
+ /** 会话行可写位(`PATCH /v1/sessions/:id`):会话店的会话面(属主 + 化身一次读 —— 门判属主、写与读回按化身条件)∧ 行写口(写后按同一住客读回)。
145
+ * 三只 durable 店(SQL 双生 / local)都带;内存 dev 店没有 ⇒ 501。
146
+ * 本面**没有**独立能力位(契约附录 B.2c 登记):老 worker 对这一动词答 405,本事实为假的部署答 501。 */
147
+ export declare function sessionPatch(d: Pick<Deps, "sessionStorage">): Fact<"session_row_faces_absent", {
148
+ sessionStorage: WithMethods<OwnerAwareSessionStore, "sessionFaceOf" | "patchSessionMeta">;
149
+ }>;
140
150
  export declare function sessionDelete(d: Pick<Deps, "purgeSession" | "sessionStorage">): Fact<"session_purge_absent" | "session_owner_face_absent", {
141
151
  purge: Store<"purgeSession">;
142
152
  sessionStorage: OwnedSessionStore;
@@ -145,10 +155,12 @@ export declare function sessionPolicy(d: Pick<Deps, "sessionPolicyStore" | "sess
145
155
  policyStore: Store<"sessionPolicyStore">;
146
156
  sessionStorage: OwnedSessionStore;
147
157
  }>;
148
- type SyncSessionStore = WithMethods<OwnerAwareSessionStore, "ownerOf" | "exportEntries" | "exportEntriesStream" | "beginImportStaging">;
149
- type SessionSyncWhy = "store_backend_absent" | "session_owner_face_absent" | "file_history_store_absent" | "session_export_absent" | "session_import_staging_absent" | "policy_purge_absent";
158
+ type SyncSessionStore = WithMethods<OwnerAwareSessionStore, "ownerOf" | "exportEntries" | "exportEntriesStream" | "beginImportStaging" | "sessionFaceOf" | "sessionSummaryOf" | "patchSessionMeta">;
159
+ type SessionSyncWhy = "store_backend_absent" | "session_owner_face_absent" | "file_history_store_absent" | "session_export_absent" | "session_import_staging_absent" | "session_row_faces_absent" | "policy_purge_absent";
150
160
  /** 会话对等同步的**对话半族**(manifest / entries / plan / import 两相):后端 ∧ 属主面 ∧ 文件历史店 ∧ 导出两面 ∧
151
- * staged 导入面 ∧ policy 店能清(overwrite-dst 的 commit 要擦目的端旧 policy 行)。文件历史的 blob 搬运是另一只事实。 */
161
+ * staged 导入面 ∧ 会话行三口(manifest 带 `title` / `pinned`、导入经唯一写口按导入落定的那一位住客重放它们 —— 会话面读住客)∧
162
+ * policy 店能清(overwrite-dst 的 commit 要擦目的端旧 policy 行)。
163
+ * 文件历史的 blob 搬运是另一只事实。 */
152
164
  export declare function sessionSync(d: Pick<Deps, "backend" | "sessionStorage" | "fileHistoryStore" | "sessionPolicyStore">): Fact<SessionSyncWhy, {
153
165
  backend: Store<"backend">;
154
166
  sessionStorage: SyncSessionStore;
@@ -290,6 +302,19 @@ export declare function workflowsJournal(d: Pick<Deps, "workflowRunStore" | "wor
290
302
  store: Store<"workflowRunStore">;
291
303
  journal: Store<"workflowJournalStore">;
292
304
  }>;
305
+ /**
306
+ * k8s 车道工作区跨轮保留的有效策略 —— k8s 车道 ∧ 快照存储在场 ⇒ `{ persist, keep }`(未表态的旋钮取缺省,缺省值属主
307
+ * `workspace-space.ts`;`persist` 缺省 `off`,opt-in `space`)。**唯一读法**:能力位 `workspaceSync` 投影它的见证,k8s 适配器工厂(`boot/execution-env.ts`)把同一份
308
+ * 见证交给适配器(适配器自己不再解析策略)。`persist: "off"` 仍是 available(存储在、没开 —— 缺省或运维显式 off)—— 与缺席不同义。
309
+ *
310
+ * **为何只读配置仍恒真**:`space` 的空间按会话住客分区,住客只有一只读口 —— 会话店的 `sessionFaceOf`。会话店没有它(内存会话店)的
311
+ * 组合在起服时就被拒(`boot/execution-env.ts` 的 `assertWorkspaceSpaceReadable`,装配第 5 段,读的正是本事实的见证)⇒ 凡是起得来、
312
+ * 报 `space` 的部署都读得出住客,本事实不需要运行期依赖(再加一位 = 同一前提两处判)。剩下的「这条 run 读不到住客」是逐 run 的读失败
313
+ * (适配器的 `unavailable` 臂,响亮一行),不是部署形。
314
+ */
315
+ export declare function workspaceSpace(c: Pick<ServiceConfig, "remoteExec">): Fact<"not_k8s_lane" | "workspace_snapshot_store_absent", {
316
+ policy: WorkspaceSyncCapability;
317
+ }>;
293
318
  /** 自唤醒调度器挂在宿主 env 上:`SCHEDULER_ENABLED` ∧ 单用户 ∧ host 车道。装配(`boot/execution-env.ts`)与能力位同调。 */
294
319
  export declare function hostScheduler(c: Pick<ServiceConfig, "schedulerEnabled" | "requirePrincipal" | "remoteExec">): Fact<"scheduler_disabled" | "multi_tenant_deployment" | "not_host_lane">;
295
320
  export {};