@sema-agent/server 7.51.0 → 7.52.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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +83 -20
- package/dist/adoption/plan.d.ts +4 -4
- package/dist/adoption/plan.js +11 -0
- package/dist/approval-card.d.ts +26 -2
- package/dist/approval-card.js +9 -0
- package/dist/approval-hmac.d.ts +1 -1
- package/dist/approval-reconciler.d.ts +2 -2
- package/dist/approval-reconciler.js +1 -1
- package/dist/bench/l8/artifact.d.ts +1 -1
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/live-deps.d.ts +1 -1
- package/dist/bench/s1/oracle.d.ts +1 -1
- package/dist/bench/s1/row.d.ts +1 -1
- package/dist/boot/budget-tracing.d.ts +1 -1
- package/dist/boot/budget-tracing.js +2 -5
- package/dist/boot/coordinators.js +3 -1
- package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
- package/dist/boot/engine-lease.d.ts +1 -1
- package/dist/boot/execution-env.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +2 -2
- package/dist/boot/leader.d.ts +1 -1
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/memory-consolidation.d.ts +6 -6
- package/dist/boot/org-memory.d.ts +1 -1
- package/dist/boot/parked-revive-gate.d.ts +3 -3
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/reapers.js +17 -4
- package/dist/boot/resolve-spec.d.ts +12 -0
- package/dist/boot/resolve-spec.js +29 -7
- package/dist/boot/retention-lane.d.ts +2 -2
- package/dist/boot/runner-deps.d.ts +17 -7
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/runtime-caps.d.ts +23 -0
- package/dist/boot/runtime-caps.js +24 -2
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +14 -3
- package/dist/boot/stores.js +31 -1
- package/dist/boot/task-list-lane.d.ts +2 -2
- package/dist/brain.js +51 -31
- package/dist/budget.d.ts +2 -2
- package/dist/capabilities/center-plugins.d.ts +1 -1
- package/dist/capabilities/collab-workflows.d.ts +1 -1
- package/dist/capabilities/hands-lane.d.ts +3 -3
- package/dist/capabilities/scenarios.d.ts +3 -3
- package/dist/capabilities/skills.d.ts +1 -1
- package/dist/config-center/hot-keys-registry.js +5 -0
- package/dist/config-center/http-client.d.ts +2 -2
- package/dist/config-center/read-face.d.ts +1 -1
- package/dist/config-center/restart-signal.d.ts +1 -1
- package/dist/config-types.d.ts +78 -18
- package/dist/config.js +45 -10
- package/dist/deployment-governance.d.ts +1 -1
- package/dist/device-enrollment.d.ts +2 -2
- package/dist/device-store.d.ts +3 -3
- package/dist/device-ws-protocol.d.ts +2 -2
- package/dist/env-facts.d.ts +1 -1
- package/dist/execution-lane-caps.d.ts +1 -1
- package/dist/fleet/fleet-bus.d.ts +1 -1
- package/dist/fleet/fleet-reconciler.d.ts +1 -1
- package/dist/fleet/fleet-terminal-window.d.ts +1 -1
- package/dist/http/active-run-conflict.d.ts +2 -2
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/route-ctx.d.ts +24 -4
- package/dist/http/routes/admin-memory-optout.d.ts +48 -0
- package/dist/http/routes/admin-memory-optout.js +75 -0
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +21 -21
- package/dist/http/routes/capabilities.js +20 -9
- package/dist/http/routes/diagnostics.d.ts +1 -1
- package/dist/http/routes/leader.d.ts +1 -1
- package/dist/http/routes/memory-bundle.d.ts +1 -1
- package/dist/http/routes/memory-bundle.js +3 -1
- package/dist/http/routes/memory-compliance.d.ts +4 -4
- package/dist/http/routes/memory-origin.d.ts +4 -4
- package/dist/http/routes/memory-origin.js +3 -1
- package/dist/http/routes/memory-policy.js +6 -2
- package/dist/http/routes/retention-ops.d.ts +1 -1
- package/dist/http/routes/rules.d.ts +5 -2
- package/dist/http/routes/rules.js +84 -5
- package/dist/http/routes/run-memory-capture.d.ts +34 -0
- package/dist/http/routes/run-memory-capture.js +97 -0
- package/dist/http/routes/runs.d.ts +1 -0
- package/dist/http/routes/runs.js +212 -6
- package/dist/http/routes/session-sync.d.ts +4 -6
- package/dist/http/routes/session-sync.js +60 -47
- package/dist/http/routes/sessions.js +61 -188
- package/dist/http/routes/shared-memory.d.ts +1 -1
- package/dist/http/routes/tasks.js +1 -1
- package/dist/http/routes/workflows.js +19 -0
- package/dist/http/server.d.ts +35 -13
- package/dist/http/server.js +105 -12
- package/dist/http/verify-rounds.d.ts +1 -1
- package/dist/http/wire-types.d.ts +41 -8
- package/dist/leader/endpoint.d.ts +1 -1
- package/dist/leader/leader.d.ts +1 -1
- package/dist/leader/repair-oracle.d.ts +2 -2
- package/dist/leader/repair-oracle.js +1 -1
- package/dist/main.js +13 -6
- package/dist/memory-bundle-engine.d.ts +1 -1
- package/dist/memory-operator-faces.d.ts +5 -5
- package/dist/memory-origin-fence.d.ts +50 -0
- package/dist/memory-origin-fence.js +15 -0
- package/dist/memory-posture.d.ts +13 -1
- package/dist/memory-posture.js +1 -0
- package/dist/memory-scope.d.ts +6 -1
- package/dist/memory-scope.js +3 -2
- package/dist/model-select.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +17 -5
- package/dist/observability/fail-open.js +17 -5
- package/dist/orchestration/hardened-vm-runner.d.ts +2 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
- package/dist/plugins/background-shell-support.d.ts +2 -2
- package/dist/plugins/blob-backend.d.ts +7 -2
- package/dist/plugins/blob-backend.js +22 -18
- package/dist/plugins/caching-session-store.d.ts +7 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.d.ts +170 -0
- package/dist/plugins/file-history-store-sql.js +827 -0
- package/dist/plugins/file-run-store.d.ts +44 -5
- package/dist/plugins/file-run-store.js +85 -13
- package/dist/plugins/fork-routing-session-store.d.ts +1 -1
- package/dist/plugins/fork-routing-session-store.js +2 -2
- package/dist/plugins/leader-run-store-sql.d.ts +1 -1
- package/dist/plugins/local-session-store.d.ts +9 -1
- package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
- package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
- package/dist/plugins/memory-origin-law.d.ts +2 -2
- package/dist/plugins/memory-run-store.d.ts +4 -3
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/permission-rule-store-file.d.ts +16 -6
- package/dist/plugins/permission-rule-store-file.js +77 -13
- package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
- package/dist/plugins/permission-rule-store-sql.js +123 -21
- package/dist/plugins/pg-pool.js +59 -0
- package/dist/plugins/pg-safe-json.d.ts +1 -1
- package/dist/plugins/remote-env-device.d.ts +1 -1
- package/dist/plugins/remote-env-k8s.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
- package/dist/plugins/retention-store-sql.d.ts +7 -7
- package/dist/plugins/retention-store-sql.js +6 -0
- package/dist/plugins/run-store-sql.d.ts +9 -3
- package/dist/plugins/run-store-sql.js +2 -2
- package/dist/plugins/send-user-file.d.ts +1 -1
- package/dist/plugins/session-placement.d.ts +1 -1
- package/dist/plugins/sql-errors.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +59 -29
- package/dist/plugins/store-backend.js +75 -15
- package/dist/plugins/store-contracts.d.ts +5 -3
- package/dist/plugins/task-list-store-sql.d.ts +1 -1
- package/dist/plugins/tidb-pool.js +59 -0
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/prompts-domain-validate.js +1 -1
- package/dist/resource-suspend.d.ts +1 -1
- package/dist/resource-window.d.ts +3 -3
- package/dist/rules-consent.d.ts +231 -9
- package/dist/rules-consent.js +411 -13
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +15 -1
- package/dist/runs.js +19 -0
- package/dist/runtime-caps-resolver.d.ts +36 -1
- package/dist/runtime-caps-resolver.js +25 -0
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/security.d.ts +5 -5
- package/dist/security.js +1 -1
- package/dist/session-sync.d.ts +52 -37
- package/dist/session-sync.js +19 -16
- package/dist/shared-memory-scope-authorizer.d.ts +1 -1
- package/dist/sighup-idle.d.ts +1 -1
- package/dist/spec-fields.d.ts +22 -1
- package/dist/spec-fields.js +11 -0
- package/dist/store-live-probe.d.ts +2 -2
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-mcp.d.ts +3 -3
- package/dist/task-settings.d.ts +3 -3
- package/dist/task-workflow.d.ts +1 -1
- package/dist/text-bidi.d.ts +2 -2
- package/dist/tool-approval.d.ts +191 -9
- package/dist/tool-approval.js +248 -29
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/engine-notice-wire.d.ts +6 -4
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/ledger-events.d.ts +1 -1
- package/dist/trace/ledger-sink.d.ts +2 -2
- package/dist/trace/project.d.ts +3 -2
- package/dist/trace/project.js +1 -0
- package/dist/turn-activity.d.ts +1 -1
- package/dist/wall-clock-jump-guard.d.ts +3 -3
- package/package.json +2 -2
- package/dist/http/tar.d.ts +0 -17
- package/dist/http/tar.js +0 -53
- package/dist/http/workspace-content.d.ts +0 -11
- package/dist/http/workspace-content.js +0 -20
- package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
- package/dist/plugins/file-snapshot-store-sql.js +0 -213
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveLockedKeys, validatePermissionRules } from "@sema-agent/core";
|
|
5
|
+
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules } from "@sema-agent/core";
|
|
6
6
|
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/settings-schema";
|
|
7
7
|
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
|
|
8
8
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
@@ -252,6 +252,15 @@ function optNumEnvStrict(name) {
|
|
|
252
252
|
return undefined;
|
|
253
253
|
return parseNumOrFail(name, raw);
|
|
254
254
|
}
|
|
255
|
+
function parseFileHistoryRetention() {
|
|
256
|
+
const raw = process.env.FILE_HISTORY_RETENTION_KEEP;
|
|
257
|
+
if (raw === undefined || raw === "")
|
|
258
|
+
return undefined;
|
|
259
|
+
const keep = raw === "unbounded" ? "unbounded" : /^(?:0|[1-9]\d*)$/.test(raw) ? Number(raw) : raw;
|
|
260
|
+
const policy = { keep };
|
|
261
|
+
resolveFileHistoryRetention("FILE_HISTORY_RETENTION_KEEP", policy);
|
|
262
|
+
return { retention: policy };
|
|
263
|
+
}
|
|
255
264
|
function optFinitePositiveEnv(name) {
|
|
256
265
|
const raw = process.env[name];
|
|
257
266
|
if (raw === undefined || raw === "")
|
|
@@ -503,10 +512,10 @@ function parseStoreDomain(ctx) {
|
|
|
503
512
|
sessionBackend,
|
|
504
513
|
sessionCacheTtlSec: numEnv("SESSION_CACHE_TTL_SEC", "300"),
|
|
505
514
|
storeProbeIntervalMs: numEnvBounded("STORE_PROBE_INTERVAL_MS", "15000", 0, 3_600_000),
|
|
506
|
-
rewindSnapshotMaxMb: optFinitePositiveEnv("REWIND_SNAPSHOT_MAX_MB"),
|
|
507
515
|
dbBackend,
|
|
508
516
|
dbBackendExplicit: dbBackendSet,
|
|
509
517
|
localDataRoot,
|
|
518
|
+
runStoreStrictHydrate: parseRunStoreStrictHydrate(process.env.RUN_STORE_STRICT_HYDRATE),
|
|
510
519
|
tidb: needsDb && dbBackend === "mysql"
|
|
511
520
|
? {
|
|
512
521
|
host: env("MYSQL_HOST"),
|
|
@@ -541,6 +550,7 @@ function parseStoreDomain(ctx) {
|
|
|
541
550
|
: undefined,
|
|
542
551
|
snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
|
|
543
552
|
snapshotBlobAllowSql: boolEnv("SNAPSHOT_BLOB_ALLOW_SQL_BYTES", false),
|
|
553
|
+
fileHistory: parseFileHistoryRetention(),
|
|
544
554
|
sendUserFile: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT) && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
|
|
545
555
|
? {
|
|
546
556
|
endpoint: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT),
|
|
@@ -803,7 +813,7 @@ export function formatUnmatchableToolNames(source, bad) {
|
|
|
803
813
|
return (`${source} names tool(s) that can never match a live tool (these lists are RAW whole-name comparisons — core 5.0.0 RB-476 removed the alias/auto-prefix folds): ` +
|
|
804
814
|
bad.map((b) => `${presentToolName(b.name)} → ${b.guidance}`).join("; ")).replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
805
815
|
}
|
|
806
|
-
function streamApprovalConfig() {
|
|
816
|
+
function streamApprovalConfig(gates) {
|
|
807
817
|
const cfg = {
|
|
808
818
|
enabled: boolEnv("STREAM_APPROVAL_ENABLED", true),
|
|
809
819
|
windowMs: numEnv("STREAM_ASK_WINDOW_MS", "300000"),
|
|
@@ -814,10 +824,15 @@ function streamApprovalConfig() {
|
|
|
814
824
|
pendingGraceMs: numEnvBounded("STREAM_APPROVAL_PENDING_GRACE_MS", "30000", 0, 3_600_000),
|
|
815
825
|
adhocGraceMs: numEnvBounded("STREAM_APPROVAL_ADHOC_GRACE_MS", "60000", 0, 86_400_000),
|
|
816
826
|
orphanTtlMs: numEnvBounded("STREAM_APPROVAL_ORPHAN_TTL_MS", String(7 * 24 * 60 * 60 * 1000), 60_000, 90 * 24 * 60 * 60 * 1000),
|
|
827
|
+
unreachedTtlMs: numEnvBounded("UNREACHED_ASK_TTL_MS", String(60 * 60 * 1000), 60_000, 24 * 60 * 60 * 1000),
|
|
817
828
|
};
|
|
818
829
|
if (cfg.windowMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
|
|
819
830
|
throw new Error(`env STREAM_ASK_WINDOW_MS=${cfg.windowMs} + STREAM_APPROVAL_ADHOC_GRACE_MS=${cfg.adhocGraceMs} must be STRICTLY LESS than STREAM_APPROVAL_ORPHAN_TTL_MS=${cfg.orphanTtlMs} — the adhoc arm fires at (createdAt + window + grace) while the orphan backstop fires at (createdAt + ttl); if the backstop wins, every adhoc-leg row is attributed "orphan_ttl_exceeded" instead of "adhoc_leg_no_durable_domain" (attribution honesty, design/172 §6.1)`);
|
|
820
831
|
}
|
|
832
|
+
const suspendedRowsPossible = cfg.enabled && gates.toolApprovalEnabled && gates.unattendedPolicy !== "deny";
|
|
833
|
+
if (suspendedRowsPossible && cfg.unreachedTtlMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
|
|
834
|
+
throw new Error(`env UNREACHED_ASK_TTL_MS=${cfg.unreachedTtlMs} + STREAM_APPROVAL_ADHOC_GRACE_MS=${cfg.adhocGraceMs} must be STRICTLY LESS than STREAM_APPROVAL_ORPHAN_TTL_MS=${cfg.orphanTtlMs} — an ask minted with NO live stream (case A: a workflow child ask) waits UNREACHED_ASK_TTL_MS, so its row expires at (createdAt + that window); the orphan backstop fires at (createdAt + ttl). If the backstop wins, every suspended row is attributed "orphan_ttl_exceeded" instead of its real cause (attribution honesty, same rule as the STREAM_ASK_WINDOW_MS line above)`);
|
|
835
|
+
}
|
|
821
836
|
if (!Number.isInteger(cfg.reconcileBatch)) {
|
|
822
837
|
throw new Error(`env STREAM_APPROVAL_RECONCILE_BATCH=${cfg.reconcileBatch} must be an INTEGER row count`);
|
|
823
838
|
}
|
|
@@ -836,6 +851,8 @@ function unattendedApprovalPolicyEnv() {
|
|
|
836
851
|
}
|
|
837
852
|
function parseApprovalDomain(ctx) {
|
|
838
853
|
const { postureOn } = ctx;
|
|
854
|
+
const unattendedPolicy = unattendedApprovalPolicyEnv();
|
|
855
|
+
const toolApprovalEnabled = postureOn("TOOL_APPROVAL_ENABLED");
|
|
839
856
|
const directApprovalDoor = boolEnv("DIRECT_APPROVAL_DOOR", false);
|
|
840
857
|
const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS, announceKeySetRejection("APPROVAL_HMAC_KEYS"));
|
|
841
858
|
const durableApproval = boolEnv("DURABLE_APPROVAL", false);
|
|
@@ -903,12 +920,12 @@ function parseApprovalDomain(ctx) {
|
|
|
903
920
|
maxTotalPerRun: numEnvBounded("QUESTION_MAX_TOTAL_PER_RUN", String(DEFAULT_QUESTION_THROTTLE.maxTotalPerRun), 1, 10_000),
|
|
904
921
|
ttlMs: numEnvBounded("QUESTION_TTL_MS", String(DEFAULT_QUESTION_THROTTLE.ttlMs), 1_000, 3_600_000),
|
|
905
922
|
},
|
|
906
|
-
toolApprovalEnabled
|
|
923
|
+
toolApprovalEnabled,
|
|
907
924
|
permissionRulesEnabled: boolEnv("PERMISSION_RULES_ENABLED", true),
|
|
908
925
|
permissionRulesEnabledExplicit: parseBoolWord("PERMISSION_RULES_ENABLED", process.env["PERMISSION_RULES_ENABLED"]) !== undefined,
|
|
909
926
|
streamAskWindowMarginMs: numEnv("STREAM_ASK_WINDOW_MARGIN_MS", "10000"),
|
|
910
|
-
streamApproval: streamApprovalConfig(),
|
|
911
|
-
unattendedApprovalPolicy:
|
|
927
|
+
streamApproval: streamApprovalConfig({ unattendedPolicy, toolApprovalEnabled }),
|
|
928
|
+
unattendedApprovalPolicy: unattendedPolicy,
|
|
912
929
|
approvalTimeoutSec: numEnv("APPROVAL_TIMEOUT_SEC", "0"),
|
|
913
930
|
};
|
|
914
931
|
}
|
|
@@ -939,6 +956,20 @@ function parseMemoryPersistenceCapable(raw) {
|
|
|
939
956
|
`\`# Memory\` write instruction on a remote execution lane) or ${MEMORY_ENGINE_OFF_WORDS.join(" | ")} (it CANNOT — ` +
|
|
940
957
|
`forces the read-only disclosure). Leave it UNSET to let the engine infer from the mounted roster.`);
|
|
941
958
|
}
|
|
959
|
+
function parseRunStoreStrictHydrate(raw) {
|
|
960
|
+
const w = (raw ?? "").trim().toLowerCase();
|
|
961
|
+
if (w === "")
|
|
962
|
+
return false;
|
|
963
|
+
if (MEMORY_ENGINE_ON_WORDS.includes(w))
|
|
964
|
+
return true;
|
|
965
|
+
if (MEMORY_ENGINE_OFF_WORDS.includes(w))
|
|
966
|
+
return false;
|
|
967
|
+
throw new Error(`env RUN_STORE_STRICT_HYDRATE="${raw}" is not a known on/off word — use one of: ${MEMORY_ENGINE_ON_WORDS.join(" | ")} ` +
|
|
968
|
+
"(the file run-store REFUSES TO BOOT when it cannot list its runs/ directory) or " +
|
|
969
|
+
`${MEMORY_ENGINE_OFF_WORDS.join(" | ")} (the default: boot anyway with an empty ledger, disclosed by the ` +
|
|
970
|
+
"file_run_store_hydrate_unreadable error line + the server.run-store.file-hydrate-unreadable counter). " +
|
|
971
|
+
"An unrecognized value must not fold to the default: that would silently swallow an operator's explicit fail-closed request.");
|
|
972
|
+
}
|
|
942
973
|
const SANDBOX_PKG_SOURCES = ["global", "cn", "custom", "none"];
|
|
943
974
|
function pkgSourceEnv() {
|
|
944
975
|
const v = env("SANDBOX_PKG_SOURCE", "global");
|
|
@@ -1144,6 +1175,9 @@ function parseMemoryDomain(ctx) {
|
|
|
1144
1175
|
...(process.env.MEMORY_PROVENANCE !== undefined
|
|
1145
1176
|
? { memoryProvenance: enumEnv("MEMORY_PROVENANCE", "carry", ["off", "carry"]) }
|
|
1146
1177
|
: {}),
|
|
1178
|
+
...(process.env.MEMORY_CAPTURE_POLICY !== undefined
|
|
1179
|
+
? { memoryCapturePolicy: enumEnv("MEMORY_CAPTURE_POLICY", "open", ["open", "governed", "capture-required"]) }
|
|
1180
|
+
: {}),
|
|
1147
1181
|
memoryConsolidationDriver: parseMemoryConsolidationDriver(),
|
|
1148
1182
|
...(process.env.DELEGATION_MAX_CONCURRENT !== undefined || process.env.DELEGATION_MAX_PER_SESSION !== undefined
|
|
1149
1183
|
? {
|
|
@@ -1669,8 +1703,9 @@ function parseA2aServeSkills(raw) {
|
|
|
1669
1703
|
});
|
|
1670
1704
|
}
|
|
1671
1705
|
const STORE_GROUP_KEYS = [
|
|
1672
|
-
"sessionBackend", "sessionCacheTtlSec", "storeProbeIntervalMs", "
|
|
1673
|
-
"
|
|
1706
|
+
"sessionBackend", "sessionCacheTtlSec", "storeProbeIntervalMs", "dbBackend", "dbBackendExplicit", "localDataRoot",
|
|
1707
|
+
"runStoreStrictHydrate",
|
|
1708
|
+
"tidb", "pg", "dbQueryTimeoutMs", "snapshotBlobStore", "snapshotBlobSqlMaxBytes", "snapshotBlobAllowSql", "fileHistory", "sendUserFile",
|
|
1674
1709
|
];
|
|
1675
1710
|
const MODEL_PLANE_GROUP_KEYS = [
|
|
1676
1711
|
"gatewayBaseUrl", "gatewayApiKey", "gatewayFallbackUrls", "gatewayMaxRetries", "anthropic", "resilience", "model", "models",
|
|
@@ -1684,7 +1719,7 @@ const APPROVAL_GROUP_KEYS = [
|
|
|
1684
1719
|
];
|
|
1685
1720
|
const MEMORY_GROUP_KEYS = [
|
|
1686
1721
|
"memoryEngineEnabled", "memoryEngineDir", "memoryEngineRemoteLaneAllowed", "memoryEngineBackend", "memoryScope",
|
|
1687
|
-
"memoryPersistenceCapable", "memorySync", "memoryEmbedder", "memoryOrgAdmissionMode", "memoryDelegationEvidence", "memoryProvenance", "memoryConsolidationDriver", "memoryOrgDirectoryJson", "memoryOrgGrantTtlMs", "memoryOrgUnavailableBackoffMs",
|
|
1722
|
+
"memoryPersistenceCapable", "memorySync", "memoryEmbedder", "memoryOrgAdmissionMode", "memoryDelegationEvidence", "memoryProvenance", "memoryCapturePolicy", "memoryConsolidationDriver", "memoryOrgDirectoryJson", "memoryOrgGrantTtlMs", "memoryOrgUnavailableBackoffMs",
|
|
1688
1723
|
"projectMemoryEnabled", "syncImportLeaseStaleSec",
|
|
1689
1724
|
];
|
|
1690
1725
|
const AUTH_GROUP_KEYS = [
|
|
@@ -1792,7 +1827,7 @@ export function loadConfig() {
|
|
|
1792
1827
|
if (dgOperators.length === 0)
|
|
1793
1828
|
missing.push("OPERATOR_PRINCIPALS (else isOperator is true-for-all)");
|
|
1794
1829
|
if (!requirePrincipal)
|
|
1795
|
-
missing.push("REQUIRE_PRINCIPAL=true (a direct door is multi-tenant; without it an absent principal is anonymous and the
|
|
1830
|
+
missing.push("REQUIRE_PRINCIPAL=true (a direct door is multi-tenant; without it an absent principal is anonymous and the self-asserted header governs owner/quota checks)");
|
|
1796
1831
|
if (missing.length) {
|
|
1797
1832
|
throw new Error(`DIRECT_APPROVAL_DOOR=true but D-G anchors are missing: ${missing.join(", ")} — refusing to start a half-open direct door (design/80 D-G boot invariant)`);
|
|
1798
1833
|
}
|
|
@@ -57,7 +57,7 @@ export interface DurableApprovalSeat {
|
|
|
57
57
|
*/
|
|
58
58
|
export declare function createDurableQuestionGate(live: LiveQuestionFace | undefined): ToolPolicy;
|
|
59
59
|
/**
|
|
60
|
-
* 沙箱 lane 上**相对形**写目标的守卫补层用 env(codex
|
|
60
|
+
* 沙箱 lane 上**相对形**写目标的守卫补层用 env(codex 交叉复审 round1 finding 1,红先复现)。
|
|
61
61
|
*
|
|
62
62
|
* 缺口:沙箱 lane 的守卫策略拿不到 `rootPath`(沙箱 cwd 不是 server 能猜的,#165 裁定 1),而
|
|
63
63
|
* `DeferredSandboxPathEnv.absolutePath` 对相对形一律报错。core 的 `canonicalizeTarget` 在
|
|
@@ -52,7 +52,7 @@ export interface DeviceEnrollRateLimiter {
|
|
|
52
52
|
export interface EnrollDeviceInput {
|
|
53
53
|
/** 明文 token(**只**在这里出现,落库前立刻摘要)。 */
|
|
54
54
|
token: string;
|
|
55
|
-
/** 请求方**验证过**的复合身份(SSO
|
|
55
|
+
/** 请求方**验证过**的复合身份(SSO/凭据链产出,恒不取自可自造头)。 */
|
|
56
56
|
requester: DeviceOwner;
|
|
57
57
|
/** 🔴 `deviceId` 与 `owner` **不在**这里:前者恒由 server 铸(调用方选 id = 让请求方挑自己的设备身份,
|
|
58
58
|
* 与 §6「server 铸 dev_<ulid>」相悖),后者恒取自**验证过的** requester —— 两个字段都不接受请求体供给。
|
|
@@ -76,7 +76,7 @@ export interface AdmitSessionValue {
|
|
|
76
76
|
/**
|
|
77
77
|
* 判决**当刻**的连接世代(§4.6 fencing token)。
|
|
78
78
|
*
|
|
79
|
-
* 🔴 **准入判决是一个快照,不是一把锁**(codex
|
|
79
|
+
* 🔴 **准入判决是一个快照,不是一把锁**(codex 交叉轮 F4,判真但归属下一车):`admitSession` 返回后、
|
|
80
80
|
* 调用方真正下发指令前,一次 `revokeDevice` 可以提交 —— 它会作废租约并把 `conn_generation` 推进一格,
|
|
81
81
|
* 但它**追不回**已经基于旧判决发出去的指令。这不是本层能关掉的窗(§6 注③ 刻意不落指令表,所以持久层
|
|
82
82
|
* 没有「派发」这个写点可以带谓词);设计给的答案是**在下发边界重新断言世代**(§4.6 步 2/3:每一次带
|
package/dist/device-store.d.ts
CHANGED
|
@@ -146,7 +146,7 @@ export declare function assertIssueTokenShape(input: IssueEnrollTokenInput): voi
|
|
|
146
146
|
export declare function assertRevokeShape(input: RevokeDeviceInput): void;
|
|
147
147
|
export declare function assertAcquireShape(input: AcquireConnectionInput): void;
|
|
148
148
|
/** token 摘要必须是**规范形**(小写 sha256 hex):大小写混形在 VARBINARY/COLLATE "C" 下是两个键,
|
|
149
|
-
* 于是同一个明文 token 能被消费两次 —— 这是 §7-T5 ③「一次性 CAS
|
|
149
|
+
* 于是同一个明文 token 能被消费两次 —— 这是 §7-T5 ③「一次性 CAS」的直接跳过面。 */
|
|
150
150
|
export declare function assertEnrollTokenHashShape(tokenHash: string): void;
|
|
151
151
|
/**
|
|
152
152
|
* 闭集列的**读侧**解码 —— 三个 `parse*` 是这些词表的唯一解码点(SQL 店读回的字符串必须经此)。
|
|
@@ -154,7 +154,7 @@ export declare function assertEnrollTokenHashShape(tokenHash: string): void;
|
|
|
154
154
|
* 🔴 认不出的值 = **抛**,不是静默 cast(#157:安全轴的未知词是 fail-loud 而不是 fail-open)。
|
|
155
155
|
* 一行 `status='disabled'` 的设备如果被 `as DeviceStatus` 静默放行,准入链的 `status === "revoked"`
|
|
156
156
|
* 判据就对它返回「不是吊销的」⇒ 一台被管理面停用的设备照常领活。写侧不可能产出这些值(入参是
|
|
157
|
-
* TS 闭集)
|
|
157
|
+
* TS 闭集),所以出现即「有人跳过本店直接写库」或「跨版本降级读新词」,两者都必须响亮。
|
|
158
158
|
*/
|
|
159
159
|
export declare function parseDeviceStatus(v: string): DeviceStatus;
|
|
160
160
|
export declare function parseDevicePlatformOs(v: string): DevicePlatformOs;
|
|
@@ -189,7 +189,7 @@ export interface IssueEnrollTokenInput {
|
|
|
189
189
|
}
|
|
190
190
|
export interface ConsumeEnrollTokenInput {
|
|
191
191
|
tokenHash: string;
|
|
192
|
-
/** 请求方**验证过**的复合身份(
|
|
192
|
+
/** 请求方**验证过**的复合身份(不取自可自造头);与 token 的 target 逐字节比对。 */
|
|
193
193
|
requester: DeviceOwner;
|
|
194
194
|
device: NewDeviceInput;
|
|
195
195
|
}
|
|
@@ -357,11 +357,11 @@ export type DeviceFrameParse = {
|
|
|
357
357
|
detail: string;
|
|
358
358
|
};
|
|
359
359
|
/**
|
|
360
|
-
* **规范** base64(codex
|
|
360
|
+
* **规范** base64(codex 交叉轮 R1-F6,判真)。
|
|
361
361
|
*
|
|
362
362
|
* 🔴 为什么不能只判「是个字符串」再交给 `Buffer.from(v, "base64")`:Node 的解码是**宽松**的 ——
|
|
363
363
|
* `"!!!!"` 解出一个**空** Buffer 而不抛,`"aG@k="` 悄悄丢掉非法字符照样解出两个字节。于是一条被
|
|
364
|
-
* 传输层损坏(
|
|
364
|
+
* 传输层损坏(或被不可信构造)的流片会以「合法的空/短数据」进入交付路径,水位照常推进、chunkAck
|
|
365
365
|
* 照常回给设备 —— 设备释放了它手上唯一的正本,调用方拿到的是**静默损坏**的输出。
|
|
366
366
|
* 在协议边界判规范形 + 回编码自比,这条路就没了。跨仓同判据:cli 侧编码器必须产规范形。
|
|
367
367
|
*/
|
package/dist/env-facts.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export declare function ensureScratchpadDir(localDataRoot: string, sessionId: st
|
|
|
76
76
|
* fs-write gate exemptDirs + additionalDirectories 根围栏)。「没到货引擎忽略 = 无害 additive」。
|
|
77
77
|
*
|
|
78
78
|
* 信任边界:这是 exemptDirs **放宽**输入,验收 fail-closed(任何拒收形回落自算,不 fault 任务):
|
|
79
|
-
* - 多租户(requirePrincipal)一律拒——共享 worker
|
|
79
|
+
* - 多租户(requirePrincipal)一律拒——共享 worker 盘上租户自选豁免目录=超范围面;
|
|
80
80
|
* - 仅 host-semantics lane——远程沙箱 lane 的 worker 本机路径对 hands 是谎言(honest-facts 同轴);
|
|
81
81
|
* - 绝对路径 + ≤{@link MAX_PATH}(core 渲染上限同源,超限=core 只能给模型假路径)+ canonical
|
|
82
82
|
* 深度 ≥3(裸 /、/tmp、/home 一整块系统目录不能当豁免域;穿越形按 resolve 归一后判);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 执行车道**闭集**与 per-lane 能力表(design device-executor-lane-v2 §4.1 收敛件,车A-1)。
|
|
3
3
|
*
|
|
4
|
-
* ##
|
|
4
|
+
* ## 根本问题:lane 词表是开放字符串上的十几处独立判别式
|
|
5
5
|
*
|
|
6
6
|
* `config.remoteExec?.provider` 的静态型是 `string | undefined` 的判别联合,但消费面从来不是一处
|
|
7
7
|
* switch —— 而是散在十几个文件里的**独立等式/集合/否定**:`cwdHonored` 比 `=== "host"`、
|
|
@@ -102,7 +102,7 @@ export interface FleetTaskRow {
|
|
|
102
102
|
transcriptId?: string;
|
|
103
103
|
/** [1839]①/(a) 案:**终态行帧的残局键**——cli 实测裸 durable-run 完成时终态 task 帧键集只有
|
|
104
104
|
* [id,name,status,tokens,elapsedMs,description],idle 观察者(web 首屏形)从行卡拿不到终局。
|
|
105
|
-
* (b) 案(为顶层 run 造 bg_notification)
|
|
105
|
+
* (b) 案(为顶层 run 造 bg_notification)被否:那族语义=观察者眼里的**子代**,自造事件族比丰富行帧
|
|
106
106
|
* 更伤([1840]§二)。四键全部 ADDITIVE / tolerate-absent、**只在终态帧出现**:
|
|
107
107
|
* - `stoppedBy`:core open enum verbatim(与 BgNotification 同姿势);顶层 run 仅 cancel 路径有源
|
|
108
108
|
* (= "user",与 markStopSource 同源值),其余终态诚实缺席。
|
|
@@ -64,7 +64,7 @@ export declare const FLEET_RECONCILE_INTERVAL_MS = 60000;
|
|
|
64
64
|
/** 每周期的**点读上限**。超出的行留到下一周期(顺序稳定 = bus 的插入序),不把一次扫描变成全表点读风暴。 */
|
|
65
65
|
export declare const FLEET_RECONCILE_MAX_ROWS = 200;
|
|
66
66
|
/** 一个周期的 durable 读**时间预算**。超时 = 放弃这一轮(记 F 类 fail-open),行留在面板上。
|
|
67
|
-
* 与 `FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS`
|
|
67
|
+
* 与 `FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS` 同值同理由:慢查询不该把整条腿卡住。 */
|
|
68
68
|
export declare const FLEET_RECONCILE_READ_BUDGET_MS = 2000;
|
|
69
69
|
/** 熔断冷却:一次读失败/超预算之后,这段时间内**不再发起任何读**。`RunStore`/`WorkflowRunStore` 契约
|
|
70
70
|
* 都没有取消面(发出去的查询撤不回),挂死的后端不该被每周期继续加压。冷却一过自动重探(自愈)。 */
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ## 为什么本模块有进程级可变状态(路由域刻意保持零状态,故不放在 `routes/fleet.ts`)
|
|
17
17
|
* 三件都长在同一个事实上——**`WorkflowRunStore` 契约没有取消面**,`Promise.race` 只结束等待、撤不回
|
|
18
18
|
* 已发出的查询:合流(重连风暴不按连接数放大,R1-H2)、合流窗有限(不把陈旧结果发给后来者、也不让
|
|
19
|
-
* 一条永不 settle
|
|
19
|
+
* 一条永不 settle 的读把后续连接全卡住,R2-H1/R3-M2)、熔断冷却(挂死的后端不被每周期继续加压,
|
|
20
20
|
* R3-H1)。真解是店侧查询超时/取消 —— 要动契约与四个后端,已作移交项。
|
|
21
21
|
*
|
|
22
22
|
* ## scope
|
|
@@ -73,7 +73,7 @@ export interface PendingGateMaterial {
|
|
|
73
73
|
* ③ **材料读取失败**:整只 `pendingGate` 都不在(那时本键连位置都没有)。
|
|
74
74
|
* ⇒ 读作「不知道这道门叫什么」,**不是**「这道门没有身份」,更**不是**「这一定是条老行」。
|
|
75
75
|
*
|
|
76
|
-
* 🔴 **它命名的是「本次 409 实际读到的那一行」,不是「activeTaskId 这条 run 的门」**(codex
|
|
76
|
+
* 🔴 **它命名的是「本次 409 实际读到的那一行」,不是「activeTaskId 这条 run 的门」**(codex 交叉复审
|
|
77
77
|
* R1-[high] 验真后如实登记):同 session 可以并存两条 pending checkpoint(表上**没有** per-session
|
|
78
78
|
* pending 唯一约束),而 `findPendingTokenBySession` 是**无序 `LIMIT 1`** —— 这个条件是本仓自己成文的
|
|
79
79
|
* 已知事实(见 `src/http/server.ts` mintFreshRun 腿 409 那段的逐字论证:「两条 pending 并存时旧
|
|
@@ -147,7 +147,7 @@ export interface ActiveRunConflictDoneResult {
|
|
|
147
147
|
/**
|
|
148
148
|
* #316(c):与 409 body 同键同语义(见 {@link ActiveRunConflictBody.stale})。
|
|
149
149
|
*
|
|
150
|
-
* 🔴 **跨仓欠账,如实登记**(codex
|
|
150
|
+
* 🔴 **跨仓欠账,如实登记**(codex 交叉复审 R1-[medium] 验真后的真形):sema-sdk 的
|
|
151
151
|
* `spec/openapi.yaml` 里 `ActiveRunConflictDoneResult` 是 `additionalProperties: false` 的**封闭**
|
|
152
152
|
* schema,今天只声明 `status/errorCode/errorMessage/activeTaskId/activeTaskStatus/pendingGate` 六键。
|
|
153
153
|
* ⚠️ 亲验后的关键修正:这**不是本键引入的新问题** —— `msSinceLastActivity`(#245 S1)早就在这条
|
|
@@ -13,7 +13,7 @@ export declare function systemFor(req: IncomingMessage, config: ServiceConfig):
|
|
|
13
13
|
export declare function bearerPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean;
|
|
14
14
|
export declare function gatedPrincipal(req: IncomingMessage, config: ServiceConfig): string | undefined;
|
|
15
15
|
/** The EXPLICIT operator gate (IMAGE-API-DESIGN.md §P2.4a MUST-FIX) — NOT the bare `isOperator` (whose empty-list
|
|
16
|
-
* "true-for-all" is
|
|
16
|
+
* "true-for-all" is an unrestricted-write exec door the instant OPERATOR_PRINCIPALS is empty). Requires a non-empty
|
|
17
17
|
* operator set AND a present principal IN it. The same form the preempt/resume endpoints already use.
|
|
18
18
|
* 🔴 SINGLE mint point (E1): a byte-identical twin `explicitOperator` used to live below (image-visibility lane)
|
|
19
19
|
* and two more hand-inlined copies sat in approvals-assistant — a future hardening of this judgment would have
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -156,18 +156,38 @@ export interface RouteLegs {
|
|
|
156
156
|
*
|
|
157
157
|
* `onTypedFailure` = 「typed 拒绝**别写响应**,交回给我」。默认(缺席)行为不变:`HttpError` 走
|
|
158
158
|
* `sendError` 连 `extra` 一起回显。DESIGN-269 车2 的 A2A 腿必须传它 —— 那些 `extra`(尤其
|
|
159
|
-
* `scenario_unknown` 的**全部场景名**)对壳是指路材料,对一个外部 peer
|
|
160
|
-
prepareSpec(req: IncomingMessage, res: ServerResponse, body?: TaskRequestBody, onTypedFailure?: (err: import("../security.js").HttpError) => void
|
|
159
|
+
* `scenario_unknown` 的**全部场景名**)对壳是指路材料,对一个外部 peer 是内部词表外泄。 */
|
|
160
|
+
prepareSpec(req: IncomingMessage, res: ServerResponse, body?: TaskRequestBody, onTypedFailure?: (err: import("../security.js").HttpError) => void,
|
|
161
|
+
/** S-46 件C:**腿身份**(不是能力表态)。`liveStream: true` 只由 `POST /v1/tasks/stream` 的装配点按
|
|
162
|
+
* URL 传 —— 那是全树唯一挂 HITL 活流投递面的腿,逐腿 `interactionPosture` 判据的第一条合取项。
|
|
163
|
+
* 缺席 ⇒ 无人腿(bg / A2A / resume),行为字节不变。 */
|
|
164
|
+
legFacts?: {
|
|
165
|
+
liveStream?: boolean;
|
|
166
|
+
}): Promise<PreparedTaskSubmission | null>;
|
|
161
167
|
/** 同步腿的终局记账(计费/配额/指标),tasks 域用。 */
|
|
162
168
|
finalizeTaskResult(result: TaskResult, principal: string | undefined, objective: string, sessionId: string | undefined): void;
|
|
163
169
|
/** approvals 决策腿:session → pending checkpoint → markResuming CAS → 驱动续跑。
|
|
164
170
|
* `settledBy` = **这次结算的出处**(core `ApprovalSettledBy`),必填:本腿有两个调用方(HTTP
|
|
165
171
|
* `/decide` = 人;内部 D-D SLA deny-sweep = 窗到期),而函数内部分辨不出谁在叫它 —— 必填参数把
|
|
166
172
|
* 「漏报出处」变成编译错,不是一条下游把「没人答」渲染成「有人拒」的静默假出处。 */
|
|
167
|
-
resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, settledBy: ApprovalSettledBy, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding,
|
|
173
|
+
resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, settledBy: ApprovalSettledBy, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding,
|
|
174
|
+
/** S-02 codex R4(验真后修):remember 豁免的 grant 挂点。**工具名不再由路由自己读**(那是与本函数
|
|
175
|
+
* 载入行分歧的第三次 scope-less 读,三读分歧下会给他人工具落豁免=提权)——由本函数把**已属主校验的
|
|
176
|
+
* 载入 cp** 的工具名(`validatedToolName`,非 tool_approval / AskUserQuestion / legacy null ⇒ `null`
|
|
177
|
+
* = 无可记)一并回传,路由只对这个逐字落店。`overrideSessionId` = parked 腿的 root/host 会话 grant 锚。 */
|
|
178
|
+
onResumeCommitted?: (grant: {
|
|
179
|
+
overrideSessionId?: string;
|
|
180
|
+
validatedToolName: string | null;
|
|
181
|
+
}) => Promise<void>,
|
|
168
182
|
/** #316:透传给 {@link DriveResumeArgs.acceptEarly}(200 受理语义)。HTTP `/decide` 腿传 `true`;
|
|
169
183
|
* 内部 D-D SLA deny-sweep 不传 —— 它没有在等响应的人,提前受理对它零收益而少一条同步失败面。 */
|
|
170
|
-
acceptEarly?: boolean
|
|
184
|
+
acceptEarly?: boolean,
|
|
185
|
+
/** S-02:调用方已验身份的裁定 —— stale 臂铸 `currentPending` 指路键前在被读回的行上重跑 decide 门
|
|
186
|
+
* 同一属主判(缺席 ⇒ 恒不造键;判据属主=server.ts 铸键点注)。 */
|
|
187
|
+
decider?: {
|
|
188
|
+
principal?: string;
|
|
189
|
+
explicitOperator: boolean;
|
|
190
|
+
}): Promise<{
|
|
171
191
|
status: number;
|
|
172
192
|
body: object;
|
|
173
193
|
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/383 §3.1 / S-50 §1.3(S-49 S-2)—— per-principal **memory-capture opt-out 授权表**的 operator 四口:
|
|
3
|
+
*
|
|
4
|
+
* · `GET /v1/admin/memory-optout` → `{ deploymentDefault: Row|null, overrides: Row[] }`
|
|
5
|
+
* · `PUT /v1/admin/memory-optout` body `{allow, note?}` → 部署缺省行 upsert(哨兵 `principal=''`)
|
|
6
|
+
* · `PUT /v1/admin/memory-optout/:principal` body `{allow, note?}` → per-principal 覆盖行 upsert
|
|
7
|
+
* · `DELETE /v1/admin/memory-optout/:principal` → 撤覆盖(回落部署缺省);`{ deleted: boolean }` 诚实答
|
|
8
|
+
*
|
|
9
|
+
* Row = `{ principal, allow, updatedAtMs, updatedBy, note? }`(审计行内,S-50 §1.3;`updatedBy` = 调用方 operator
|
|
10
|
+
* principal,server 铸,body 不收)。
|
|
11
|
+
*
|
|
12
|
+
* ── 稿偏离两条(如实记)────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
* ① S-50 §1.1/§1.3 写的是 `orgDefault` + `(org_id, principal)`。亲读本仓(2026-08-30 侦察):server **没有 org 身份维**
|
|
14
|
+
* (租户维 = 单 principal;`org` 只是记忆 scope 目录里的词),给表/wire 铸一根没人能填的 `org_id` 是编数据。一台
|
|
15
|
+
* worker = 一个租户面 ⇒ 「org 级缺省」落成**部署级缺省**,wire 键名随实体改叫 `deploymentDefault`。折叠律不变。
|
|
16
|
+
* ② S-50 §1.3 把部署缺省写成 `PUT …/memory-optout/default`。codex R1-[medium] 验真:`default` 是一个**合法 principal
|
|
17
|
+
* 拼写**,字面路径 `/default` 与 `%64efault`(解码后同为 `default`)在第一版里分派到**两个不同的行**(哨兵行 vs
|
|
18
|
+
* 一条名叫 default 的覆盖行)—— 命名空间碰撞,而且 DELETE 侧还不对称。修法不是「解码后再保留一个词」(那只是把
|
|
19
|
+
* 碰撞改成一条不可寻址的 principal),而是**让缺省行住在集合根**:`PUT /v1/admin/memory-optout`(集合本身的
|
|
20
|
+
* 「缺省」属性),`:principal` 面从此没有任何保留词,任何合法 principal(包括字面 `default`)都可寻址。
|
|
21
|
+
*
|
|
22
|
+
* ── 族属与门序 ────────────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
* 与 `/v1/admin/drain` · `/v1/admin/config/refresh` · `/v1/admin/memory/consolidation` 逐字同族(operator lane:
|
|
24
|
+
* 同一条 `explicitOperatorOk` 门;billable=false;刻意不吃 drain / roster-pending 两道 503)。门序照 operator
|
|
25
|
+
* 面既有口径:身份(401)→ 授权(403)→ **能力(501)** → 验型(400)。授权在能力之前:一个够不着任何东西
|
|
26
|
+
* 的调用方不该从「这台有没有授权表」上读出部署形态。
|
|
27
|
+
* 🔴 四口都在 `isCredentialGatedRewrite`(server.ts)门内(codex R1-[high] 验真后补):授权的**唯一**输入是
|
|
28
|
+
* principal 头 ∧ 一次调用的爆炸半径跨租户(改缺省行 = 改整台部署每个 principal 的 verdict;读面 = 全部授权行 +
|
|
29
|
+
* operator 手写的 note)—— 无 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` 的 worker 上那个头**可自造**,与
|
|
30
|
+
* `/v1/memory/erase` / `POST /v1/memory/export` 同一条论证。
|
|
31
|
+
*
|
|
32
|
+
* ── 为什么缺席形是 **501 `capability.*`** 而不是 404(与 consolidation 阀门刻意分家)──────────────────
|
|
33
|
+
* consolidation 的 404 说的是「旋钮没开」(运维表态,动作=开旋钮)。本面的缺席是「**这家店在本部署上不存在**」
|
|
34
|
+
* —— local 车道刻意不给授权表一个会遗忘的 in-memory twin(接口注),动作=换部署形态(接 SQL 后端)。那正是
|
|
35
|
+
* 本仓 501 `capability.*` 族的定义(`capability.memory_engine_required` / `capability.outcome_ledger_required`
|
|
36
|
+
* 同族)。能力位 `memoryOptOutGrant`(`GET /v1/capabilities`)读同一个事实 + operator 名单非空。
|
|
37
|
+
*
|
|
38
|
+
* ── 撤销窗(S-50 §1.4)──────────────────────────────────────────────────────────────────────────
|
|
39
|
+
* 写后**零缓存**:resolver 腿(`RuntimeCaps.allowMemoryOptOut`)每次 prepare 直读同一张表 ⇒ 下一次带声明的
|
|
40
|
+
* prepare 即读到新 verdict(撤权后新声明具名拒;已声明会话 record wins,core 判,server 零码)。
|
|
41
|
+
*/
|
|
42
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
43
|
+
import type { RouteCtx } from "../route-ctx.js";
|
|
44
|
+
export declare const ADMIN_MEMORY_OPTOUT_PATH = "/v1/admin/memory-optout";
|
|
45
|
+
/** 带参口(`:principal`)⇒ 名册门走 `ROUTE_LABEL_PATTERNS`(server.ts),不是 LITERALS。 */
|
|
46
|
+
export declare const ADMIN_MEMORY_OPTOUT_OVERRIDE_RE: RegExp;
|
|
47
|
+
export declare function handleMemoryOptOut(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
|
|
48
|
+
//# sourceMappingURL=admin-memory-optout.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sendJson, sendError } from "../send.js";
|
|
3
|
+
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
4
|
+
import { memoryOptOutPrincipalShapeProblem } from "../../plugins/memory-optout-grant-store-sql.js";
|
|
5
|
+
export const ADMIN_MEMORY_OPTOUT_PATH = "/v1/admin/memory-optout";
|
|
6
|
+
export const ADMIN_MEMORY_OPTOUT_OVERRIDE_RE = /^\/v1\/admin\/memory-optout\/([^/]+)$/;
|
|
7
|
+
const PutBodySchema = z.object({ allow: z.boolean(), note: z.string().min(1).max(500).optional() }).strict();
|
|
8
|
+
export async function handleMemoryOptOut(req, res, url, ctx) {
|
|
9
|
+
const miss = { fell: false };
|
|
10
|
+
await handleBody(req, res, url, ctx, miss);
|
|
11
|
+
return !miss.fell;
|
|
12
|
+
}
|
|
13
|
+
function decideVerb(method, path, safeDecode) {
|
|
14
|
+
if (path === ADMIN_MEMORY_OPTOUT_PATH) {
|
|
15
|
+
if (method === "GET")
|
|
16
|
+
return { kind: "list" };
|
|
17
|
+
if (method === "PUT")
|
|
18
|
+
return { kind: "putDefault" };
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const m = ADMIN_MEMORY_OPTOUT_OVERRIDE_RE.exec(path);
|
|
22
|
+
if (m === null)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (method !== "PUT" && method !== "DELETE")
|
|
25
|
+
return undefined;
|
|
26
|
+
const principal = safeDecode(m[1]);
|
|
27
|
+
if (principal === null)
|
|
28
|
+
return "malformed";
|
|
29
|
+
return method === "PUT" ? { kind: "putOverride", principal } : { kind: "deleteOverride", principal };
|
|
30
|
+
}
|
|
31
|
+
async function handleBody(req, res, url, ctx, miss) {
|
|
32
|
+
const { deps } = ctx;
|
|
33
|
+
const path = url.split("?")[0] ?? url;
|
|
34
|
+
const verb = decideVerb(req.method, path, ctx.helpers.safeDecode);
|
|
35
|
+
if (verb === undefined) {
|
|
36
|
+
miss.fell = true;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
40
|
+
if (deps.config.requirePrincipal && !principal) {
|
|
41
|
+
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
45
|
+
sendError(res, 403, "auth.operator_only", "memory opt-out grant administration is operator-only: the caller principal is not in OPERATOR_PRINCIPALS (an empty list means NO one is an operator — configure it to enable these endpoints)");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const operator = principal;
|
|
49
|
+
const store = deps.memoryOptOutGrant;
|
|
50
|
+
if (store === undefined) {
|
|
51
|
+
sendError(res, 501, "capability.memory_optout_grant_required", "this deployment has no memory opt-out grant table: the per-principal memory-capture opt-out authorization store lives on a SQL store backend (DB_BACKEND=mysql|pg) and the local backend deliberately keeps no forgetting twin — wire a SQL backend to administer opt-out grants");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (verb === "malformed" || ((verb.kind === "putOverride" || verb.kind === "deleteOverride") && memoryOptOutPrincipalShapeProblem(verb.principal) !== undefined)) {
|
|
55
|
+
sendError(res, 400, "request.path_malformed", "invalid memory opt-out grant principal — the path segment must percent-decode to a non-empty principal of at most 190 chars with no leading/trailing whitespace or control characters (the deployment default row is addressed by PUT on the collection root, not by a path segment)");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (verb.kind === "list") {
|
|
59
|
+
sendJson(res, 200, await store.list());
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (verb.kind === "deleteOverride") {
|
|
63
|
+
sendJson(res, 200, { principal: verb.principal, deleted: await store.deleteOverride(verb.principal) });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const parsed = PutBodySchema.safeParse(await ctx.helpers.readJson(req));
|
|
67
|
+
if (!parsed.success) {
|
|
68
|
+
sendError(res, 400, "request.body_shape", "invalid memory opt-out grant body — expected exactly { allow: boolean, note?: string (1..500 chars) }; unknown keys are refused (a mistyped `allow` must not silently become a no-op)");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const input = { allow: parsed.data.allow, updatedBy: operator, ...(parsed.data.note !== undefined ? { note: parsed.data.note } : {}) };
|
|
72
|
+
const row = verb.kind === "putDefault" ? await store.putDefault(input) : await store.putOverride(verb.principal, input);
|
|
73
|
+
sendJson(res, 200, row);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=admin-memory-optout.js.map
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* ## 三条边界(逐条对着失败形写)
|
|
18
18
|
* · **principal 门 + 反枚举**:身份判在店可用性判**之前**(否则「这台有没有装店」会先于 401 泄出去,
|
|
19
|
-
* 与 `GET /v1/sessions` 的同款注释);principal
|
|
19
|
+
* 与 `GET /v1/sessions` 的同款注释);principal 调用方**锁定自己的 scope**(`?scope=` 对它无效),
|
|
20
20
|
* 于是别的租户的行不是「403」也不是「空 200 但 total 多 1」——它在这个名册里**根本不存在**,
|
|
21
21
|
* 没有任何可判别面(`total` 同样是属主口径:一个多出来的计数就是存在性 oracle)。
|
|
22
22
|
* · **投影白名单**(照 `routes/fleet.ts` `stripT` 的姿势):逐键**显式拼**,并用
|
|
@@ -38,6 +38,8 @@ async function decideIdempotentReplay(cs, args) {
|
|
|
38
38
|
const boundCallId = binding.boundCallId;
|
|
39
39
|
if (boundCallId === undefined)
|
|
40
40
|
return undefined;
|
|
41
|
+
if (binding.checkpointToken !== undefined)
|
|
42
|
+
return undefined;
|
|
41
43
|
if (typeof cs.findDecidedApprovalsForBinding !== "function")
|
|
42
44
|
return undefined;
|
|
43
45
|
const rows = await cs.findDecidedApprovalsForBinding(sessionId, boundCallId);
|
|
@@ -463,30 +465,28 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
463
465
|
return;
|
|
464
466
|
}
|
|
465
467
|
}
|
|
466
|
-
let rememberToolName = null;
|
|
467
|
-
if (remember) {
|
|
468
|
-
const tok = await cs.findPendingTokenBySession(sessionId);
|
|
469
|
-
const cp0 = tok ? await cs.get(tok) : null;
|
|
470
|
-
const pa0 = cp0?.pendingAction;
|
|
471
|
-
const tn = pa0 && pa0.kind === "tool_approval" ? pa0.toolName : null;
|
|
472
|
-
rememberToolName = tn && tn !== "AskUserQuestion" ? tn : null;
|
|
473
|
-
}
|
|
474
468
|
let rememberApplied = false;
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
deps.logger?.info?.("approval_exemption_granted", { sessionId: grantSessionId, toolName: canonical, grantedBy: deciderPrincipal ?? null });
|
|
483
|
-
}
|
|
484
|
-
catch (e) {
|
|
485
|
-
deps.logger?.warn?.("approval_exemption_grant_failed", { sessionId: grantSessionId, toolName: rememberToolName, error: String(e).slice(0, 200) });
|
|
486
|
-
}
|
|
469
|
+
let pendingGrant;
|
|
470
|
+
const grantOnCommit = remember && deps.approvalExemptionStore
|
|
471
|
+
? async (grant) => {
|
|
472
|
+
const canonical = grant.validatedToolName;
|
|
473
|
+
if (!canonical)
|
|
474
|
+
return;
|
|
475
|
+
pendingGrant = { sessionId: grant.overrideSessionId ?? sessionId, toolName: canonical };
|
|
487
476
|
}
|
|
488
477
|
: undefined;
|
|
489
|
-
const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, "human", req, answer, binding, grantOnCommit, true);
|
|
478
|
+
const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, "human", req, answer, binding, grantOnCommit, true, { principal: deciderPrincipal, explicitOperator });
|
|
479
|
+
const outErrorCode = out.body.errorCode;
|
|
480
|
+
if (pendingGrant !== undefined && out.status === 200 && outErrorCode !== "cancelled" && deps.approvalExemptionStore) {
|
|
481
|
+
try {
|
|
482
|
+
await deps.approvalExemptionStore.grant(pendingGrant.sessionId, pendingGrant.toolName, deciderPrincipal ?? null);
|
|
483
|
+
rememberApplied = true;
|
|
484
|
+
deps.logger?.info?.("approval_exemption_granted", { sessionId: pendingGrant.sessionId, toolName: pendingGrant.toolName, grantedBy: deciderPrincipal ?? null });
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
deps.logger?.warn?.("approval_exemption_grant_failed", { sessionId: pendingGrant.sessionId, toolName: pendingGrant.toolName, error: String(e).slice(0, 200) });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
490
|
const replay = await decideIdempotentReplay(cs, {
|
|
491
491
|
sessionId,
|
|
492
492
|
out,
|
|
@@ -9,6 +9,7 @@ import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
|
9
9
|
import { sendJson, sendError } from "../send.js";
|
|
10
10
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
11
11
|
import { projectMemoryEngineCapability } from "../../memory-posture.js";
|
|
12
|
+
import { workflowModelAllowlistFor } from "../../task-workflow.js";
|
|
12
13
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
13
14
|
import { gatedPrincipal } from "../principal-gate.js";
|
|
14
15
|
function routePairingOf(deps, name, m) {
|
|
@@ -55,24 +56,21 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
55
56
|
fleet: deps.fleetBus
|
|
56
57
|
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "advisory", hookFailureNotice: true }
|
|
57
58
|
: false,
|
|
58
|
-
workspace:
|
|
59
|
-
const wfs = deps.fileSnapshotStore;
|
|
60
|
-
return wfs && typeof wfs.listKeys === "function" && typeof wfs.exportManifest === "function" && typeof wfs.getBlob === "function" && Boolean(deps.sessionStorage?.ownerOf)
|
|
61
|
-
? { browse: true, archive: true, maxFileBytes: deps.config.workspaceFileMaxBytes ?? 8 * 1024 * 1024 }
|
|
62
|
-
: false;
|
|
63
|
-
})(),
|
|
59
|
+
workspace: false,
|
|
64
60
|
leader: Boolean(deps.leaderEndpoint),
|
|
65
61
|
images: Boolean(deps.imageIndex),
|
|
66
62
|
memory: false,
|
|
67
63
|
memoryWrite: false,
|
|
68
64
|
memoryEngine: projectMemoryEngineCapability(deps.memoryPosture),
|
|
69
65
|
sessionBackgroundable: Boolean(deps.runStore),
|
|
66
|
+
runMemoryCaptureOptOut: Boolean(deps.runStore),
|
|
70
67
|
sharedMemory: Boolean(deps.sharedMemoryStore && deps.orgMemoryDirectory),
|
|
71
68
|
adoption: Boolean(deps.backend?.adoptionLog) && deps.config.operatorPrincipals.length > 0,
|
|
72
69
|
memoryBundle: Boolean(deps.memoryBundleExport && deps.memoryBundleImport) && deps.config.operatorPrincipals.length > 0,
|
|
73
70
|
memoryCompliance: Boolean(deps.memoryCompliance) && deps.config.operatorPrincipals.length > 0,
|
|
74
71
|
memoryOrigin: Boolean(deps.memoryOriginFace) && deps.config.operatorPrincipals.length > 0,
|
|
75
72
|
memoryConsolidationDriver: Boolean(deps.memoryConsolidation) && deps.config.operatorPrincipals.length > 0,
|
|
73
|
+
memoryOptOutGrant: Boolean(deps.memoryOptOutGrant) && deps.config.operatorPrincipals.length > 0,
|
|
76
74
|
outcomeLedger: Boolean(deps.outcomeSink?.summary) && (deps.config.requirePrincipal !== true || deps.config.operatorPrincipals.length > 0),
|
|
77
75
|
permissionRules: Boolean(deps.ruleConsent) && gatedPrincipal(req, deps.config) !== undefined,
|
|
78
76
|
permissionRulesRevoke: Boolean(deps.ruleConsent),
|
|
@@ -135,15 +133,28 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
135
133
|
resumeAt: Boolean(deps.resumeAnchorStore && deps.sessionStorage?.getLeafId),
|
|
136
134
|
promptSuggestions: true,
|
|
137
135
|
sessionPolicy: Boolean(deps.sessionPolicyStore),
|
|
138
|
-
rewindFiles: Boolean(deps.
|
|
139
|
-
|
|
136
|
+
rewindFiles: Boolean(deps.fileHistoryStore),
|
|
137
|
+
restoreFiles: Boolean(deps.fileHistoryStore),
|
|
138
|
+
rewindFilesTo: Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && deps.sessionStorage?.getLeafId),
|
|
140
139
|
scheduler: Boolean(deps.config.schedulerEnabled && deps.config.requirePrincipal !== true && deps.config.remoteExec?.provider === "host"),
|
|
141
|
-
sessionSync: Boolean(deps.backend && deps.sessionStorage?.exportEntries && deps.sessionStorage?.beginImportStaging && deps.
|
|
140
|
+
sessionSync: Boolean(deps.backend && deps.sessionStorage?.exportEntries && deps.sessionStorage?.beginImportStaging && deps.fileHistoryStore),
|
|
142
141
|
sendUserFile: Boolean(deps.sendUserFile && deps.config.sendUserFile?.publicEndpoint),
|
|
143
142
|
s3PublicEndpoint: deps.config.sendUserFile?.publicEndpoint ?? null,
|
|
144
143
|
sendUserFileLedger: Boolean(deps.sendFileLedger),
|
|
145
144
|
workflows: deps.workflowsCapable ?? Boolean(deps.workflowRunStore),
|
|
146
145
|
workflowsList: Boolean(deps.workflowRunStore),
|
|
146
|
+
workflowModels: (() => {
|
|
147
|
+
const allow = deps.workflowModelAllowlist
|
|
148
|
+
?? workflowModelAllowlistFor({
|
|
149
|
+
selfOrchestrationModels: deps.config.selfOrchestrationModels ?? [],
|
|
150
|
+
requirePrincipal: deps.config.requirePrincipal,
|
|
151
|
+
models: deps.config.models ?? {},
|
|
152
|
+
})
|
|
153
|
+
?? [];
|
|
154
|
+
const bare = deps.config.models ?? {};
|
|
155
|
+
const live = expandTiers(bare, deps.config.tiers ?? {}) ?? bare;
|
|
156
|
+
return allow.filter((n) => Object.hasOwn(live, n));
|
|
157
|
+
})(),
|
|
147
158
|
manualCompact: Boolean(deps.runStore),
|
|
148
159
|
});
|
|
149
160
|
return;
|