@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
const SEP = "\u0000";
|
|
2
|
-
/** S9 deeper fix: default per-query timeout for the counter family's statements. Always ON (unlike the
|
|
3
|
-
* opt-in pool-wide DB_QUERY_TIMEOUT_MS): every statement here is a tiny single-row/single-bucket op, so
|
|
4
|
-
* 30s is ~1000× headroom — a query that slow IS the S9 pathology (a DB-side hang would otherwise pin the
|
|
5
|
-
* flush round forever; the stalled watchdog can then only report, not recover). The rejection lands in
|
|
6
|
-
* the existing flush/refresh catch → the S9 streak telemetry counts it; the round ENDS and retries. */
|
|
1
|
+
const SEP = "\u0000";
|
|
7
2
|
export const DEFAULT_COUNTER_QUERY_TIMEOUT_MS = 30_000;
|
|
8
|
-
/** Dual-dialect write-behind aggregating counter. See the file header for the dialect-delta ledger. */
|
|
9
3
|
export class SqlWriteBehindCounter {
|
|
10
4
|
dialect;
|
|
11
5
|
pool;
|
|
@@ -14,20 +8,13 @@ export class SqlWriteBehindCounter {
|
|
|
14
8
|
now;
|
|
15
9
|
onDegraded;
|
|
16
10
|
queryTimeoutMs;
|
|
17
|
-
pendingDelta = new Map();
|
|
18
|
-
fleetTotal = new Map();
|
|
11
|
+
pendingDelta = new Map();
|
|
12
|
+
fleetTotal = new Map();
|
|
19
13
|
timer;
|
|
20
14
|
flushing = false;
|
|
21
|
-
flushStartedAt = 0;
|
|
22
|
-
failStreak = new Map();
|
|
23
|
-
constructor(dialect, pool, t,
|
|
24
|
-
/** 🔴 不再 `readonly`(#322):`costQuotaWindowSec` 热更新经 {@link setWindowMs} 换代,语义见那里。 */
|
|
25
|
-
windowMs, now = () => Date.now(), onDegraded, // S9: observability tap — never affects counting
|
|
26
|
-
// S9 deeper fix: per-query inactivity timeout. mysql2's is the client-side `{sql, timeout}` form (the
|
|
27
|
-
// connection is held until the hung command really ends, so a poisoned connection is never reused
|
|
28
|
-
// mid-query); node-pg's is `query_timeout` on the QueryConfig. Overridable via DB_QUERY_TIMEOUT_MS
|
|
29
|
-
// (threaded by store-backend.ts); the default keeps the counter face always bounded.
|
|
30
|
-
queryTimeoutMs = DEFAULT_COUNTER_QUERY_TIMEOUT_MS) {
|
|
15
|
+
flushStartedAt = 0;
|
|
16
|
+
failStreak = new Map();
|
|
17
|
+
constructor(dialect, pool, t, windowMs, now = () => Date.now(), onDegraded, queryTimeoutMs = DEFAULT_COUNTER_QUERY_TIMEOUT_MS) {
|
|
31
18
|
this.dialect = dialect;
|
|
32
19
|
this.pool = pool;
|
|
33
20
|
this.t = t;
|
|
@@ -36,7 +23,6 @@ export class SqlWriteBehindCounter {
|
|
|
36
23
|
this.onDegraded = onDegraded;
|
|
37
24
|
this.queryTimeoutMs = queryTimeoutMs;
|
|
38
25
|
}
|
|
39
|
-
/** The ONLY place the two drivers' query shapes diverge. Returns {rows, affected}. */
|
|
40
26
|
async exec(sql, params) {
|
|
41
27
|
if (this.dialect === "tidb") {
|
|
42
28
|
const [r] = (await this.pool.query({ sql, timeout: this.queryTimeoutMs }, params));
|
|
@@ -48,8 +34,6 @@ export class SqlWriteBehindCounter {
|
|
|
48
34
|
const res = await this.pool.query(q);
|
|
49
35
|
return { rows: (res.rows ?? []), affected: res.rowCount ?? 0 };
|
|
50
36
|
}
|
|
51
|
-
/** S9: track the consecutive-failure streak per kind and notify on every failure + on the recovery edge
|
|
52
|
-
* (streak>0 → 0). Steady healthy state stays silent. The hook must never break the counter. */
|
|
53
37
|
bump(kind, failed, error) {
|
|
54
38
|
if (!this.onDegraded)
|
|
55
39
|
return;
|
|
@@ -62,7 +46,6 @@ export class SqlWriteBehindCounter {
|
|
|
62
46
|
this.onDegraded({ table: this.t.table, kind, streak: next, prevStreak: prev, ...(failed && error !== undefined ? { error: error instanceof Error ? error.message : String(error) } : {}) });
|
|
63
47
|
}
|
|
64
48
|
catch {
|
|
65
|
-
/* observability must never break the counter */
|
|
66
49
|
}
|
|
67
50
|
}
|
|
68
51
|
bucket() {
|
|
@@ -71,7 +54,6 @@ export class SqlWriteBehindCounter {
|
|
|
71
54
|
rk(key) {
|
|
72
55
|
return `${key}${SEP}${this.bucket()}`;
|
|
73
56
|
}
|
|
74
|
-
/** Cached fleet total (peers, from the last refresh) + this replica's unflushed delta, current window. */
|
|
75
57
|
used(key) {
|
|
76
58
|
const rk = this.rk(key);
|
|
77
59
|
return (this.fleetTotal.get(rk) ?? 0) + (this.pendingDelta.get(rk) ?? 0);
|
|
@@ -82,20 +64,11 @@ export class SqlWriteBehindCounter {
|
|
|
82
64
|
const rk = this.rk(key);
|
|
83
65
|
this.pendingDelta.set(rk, (this.pendingDelta.get(rk) ?? 0) + n);
|
|
84
66
|
}
|
|
85
|
-
/** Seconds until the current aligned window rolls over. */
|
|
86
67
|
retryAfterSec() {
|
|
87
68
|
return Math.ceil(((this.bucket() + 1) * this.windowMs - this.now()) / 1000);
|
|
88
69
|
}
|
|
89
|
-
/** Atomically push pending deltas, then refresh the current window's fleet totals + reap old buckets.
|
|
90
|
-
* Reentrancy-guarded: two overlapping flushes would each apply `+delta` from the same snapshot and
|
|
91
|
-
* double-count into the shared table (inflating fleet totals → spurious rate-limit/quota rejection). */
|
|
92
70
|
async flush() {
|
|
93
71
|
if (this.flushing) {
|
|
94
|
-
// S9 review MED-1: a hung query holds this guard: every later tick returns HERE and bump() is never
|
|
95
|
-
// reached, so the reject-path telemetry goes silent exactly when the fleet is degraded. Surface the
|
|
96
|
-
// stall itself — >60s inside one flush round = a "stalled" failure signal per tick; its streak clears
|
|
97
|
-
// when the round finally settles. (The per-query timeout above is the deeper fix — a round now self-
|
|
98
|
-
// terminates at queryTimeoutMs; this watchdog stays as the backstop for anything it doesn't cover.)
|
|
99
72
|
if (this.now() - this.flushStartedAt > 60_000)
|
|
100
73
|
this.bump("stalled", true, "flush round stalled >60s (hung query?)");
|
|
101
74
|
return;
|
|
@@ -107,18 +80,17 @@ export class SqlWriteBehindCounter {
|
|
|
107
80
|
}
|
|
108
81
|
finally {
|
|
109
82
|
this.flushing = false;
|
|
110
|
-
this.bump("stalled", false);
|
|
83
|
+
this.bump("stalled", false);
|
|
111
84
|
}
|
|
112
85
|
}
|
|
113
86
|
async flushOnce() {
|
|
114
87
|
const { table, keyCol, valCol } = this.t;
|
|
115
|
-
// The atomic-increment upsert — the ONE statement whose dialect delta actually matters here.
|
|
116
88
|
const sql = this.dialect === "tidb"
|
|
117
89
|
? `INSERT INTO ${table} (${keyCol}, window_bucket, ${valCol}, updated_at) VALUES (?,?,?,?) ` +
|
|
118
90
|
`ON DUPLICATE KEY UPDATE ${valCol} = ${valCol} + VALUES(${valCol}), updated_at = VALUES(updated_at)`
|
|
119
91
|
: `INSERT INTO ${table} (${keyCol}, window_bucket, ${valCol}, updated_at) VALUES ($1,$2,$3,$4) ` +
|
|
120
92
|
`ON CONFLICT (${keyCol}, window_bucket) DO UPDATE SET ${valCol} = ${table}.${valCol} + EXCLUDED.${valCol}, updated_at = EXCLUDED.updated_at`;
|
|
121
|
-
let attempted = false;
|
|
93
|
+
let attempted = false;
|
|
122
94
|
let failure;
|
|
123
95
|
let hadFailure = false;
|
|
124
96
|
for (const [rk, delta] of [...this.pendingDelta]) {
|
|
@@ -129,8 +101,7 @@ export class SqlWriteBehindCounter {
|
|
|
129
101
|
const windowBucket = Number(rk.slice(sep + 1));
|
|
130
102
|
attempted = true;
|
|
131
103
|
try {
|
|
132
|
-
await this.exec(sql, [key, windowBucket, delta, new Date()]);
|
|
133
|
-
// subtract what we flushed (not delete — a concurrent addLocal may have grown it meanwhile)
|
|
104
|
+
await this.exec(sql, [key, windowBucket, delta, new Date()]);
|
|
134
105
|
const remaining = (this.pendingDelta.get(rk) ?? 0) - delta;
|
|
135
106
|
if (remaining > 0)
|
|
136
107
|
this.pendingDelta.set(rk, remaining);
|
|
@@ -138,22 +109,15 @@ export class SqlWriteBehindCounter {
|
|
|
138
109
|
this.pendingDelta.delete(rk);
|
|
139
110
|
}
|
|
140
111
|
catch (err) {
|
|
141
|
-
hadFailure = true;
|
|
112
|
+
hadFailure = true;
|
|
142
113
|
failure = err;
|
|
143
|
-
// keep the delta; retry next flush (advisory — never throw into the caller).
|
|
144
|
-
// KNOWN/ACCEPTED (audit B): an ambiguous failure (INSERT applied but the ack was lost) re-adds
|
|
145
|
-
// the delta next flush = double-count; conversely a reader between our INSERT and a peer's sees
|
|
146
|
-
// a momentary under-count. Both are bounded by one flush window and this counter backs SOFT
|
|
147
|
-
// limits only (quota/rate-limit, design: eventual consistency; hard caps use per-task budgets).
|
|
148
|
-
// Exactly-once would need a flush ledger/CAS — not worth it at this tier.
|
|
149
114
|
}
|
|
150
115
|
}
|
|
151
116
|
if (attempted)
|
|
152
|
-
this.bump("flush", hadFailure, failure);
|
|
117
|
+
this.bump("flush", hadFailure, failure);
|
|
153
118
|
await this.refresh();
|
|
154
119
|
await this.reap();
|
|
155
120
|
}
|
|
156
|
-
/** Load the current window's fleet totals into the cache (pendingDelta is separate → never clobbered). */
|
|
157
121
|
async refresh() {
|
|
158
122
|
const { table, keyCol, valCol } = this.t;
|
|
159
123
|
const b = this.bucket();
|
|
@@ -163,36 +127,26 @@ export class SqlWriteBehindCounter {
|
|
|
163
127
|
: `SELECT ${keyCol}, ${valCol} FROM ${table} WHERE window_bucket = $1`, [b]);
|
|
164
128
|
const next = new Map();
|
|
165
129
|
for (const row of rows) {
|
|
166
|
-
next.set(`${String(row[keyCol])}${SEP}${b}`, Number(row[valCol]));
|
|
130
|
+
next.set(`${String(row[keyCol])}${SEP}${b}`, Number(row[valCol]));
|
|
167
131
|
}
|
|
168
132
|
this.fleetTotal = next;
|
|
169
|
-
this.bump("refresh", false);
|
|
133
|
+
this.bump("refresh", false);
|
|
170
134
|
}
|
|
171
135
|
catch (err) {
|
|
172
|
-
|
|
173
|
-
this.bump("refresh", true, err); // S9
|
|
136
|
+
this.bump("refresh", true, err);
|
|
174
137
|
}
|
|
175
138
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Delete expired window buckets to bound the table.
|
|
178
|
-
*
|
|
179
|
-
* 🔴 失败**必须留痕**(#192 codex 复审 round2 修):这里原先是裸 `catch { return 0 }` —— 一个只影响
|
|
180
|
-
* DELETE 的故障(权限被收回、引擎单独拒 DELETE)会被吞成"删了 0 行",而 flush/refresh 两条腿照常成功
|
|
181
|
-
* ⇒ 健康遥测全绿,同时 rate_limit / cost_quota 的历史桶无界增长。那正是本仓禁止的静默 fail-open。
|
|
182
|
-
* 处置是**留痕不改失败方向**:清理是 best-effort,失败仍返回 0(把一次运维层面的清理失败升级成业务
|
|
183
|
-
* 失败是错的方向),但走与 flush/refresh 同一套 `bump()` ⇒ 有连败计数、有降级钩子、有恢复边沿。
|
|
184
|
-
*/
|
|
185
139
|
async reap() {
|
|
186
140
|
try {
|
|
187
141
|
const { affected } = await this.exec(this.dialect === "tidb"
|
|
188
142
|
? `DELETE FROM ${this.t.table} WHERE window_bucket < ?`
|
|
189
143
|
: `DELETE FROM ${this.t.table} WHERE window_bucket < $1`, [this.bucket()]);
|
|
190
|
-
this.bump("reap", false);
|
|
144
|
+
this.bump("reap", false);
|
|
191
145
|
return affected;
|
|
192
146
|
}
|
|
193
147
|
catch (err) {
|
|
194
148
|
this.bump("reap", true, err);
|
|
195
|
-
return 0;
|
|
149
|
+
return 0;
|
|
196
150
|
}
|
|
197
151
|
}
|
|
198
152
|
startRefresh(intervalMs = 5_000) {
|
|
@@ -205,31 +159,18 @@ export class SqlWriteBehindCounter {
|
|
|
205
159
|
clearInterval(this.timer);
|
|
206
160
|
this.timer = undefined;
|
|
207
161
|
}
|
|
208
|
-
/** #322:刷新环是否在跑(关断哨兵的判据:off 态必须**零 SQL**,含这条后台环)。 */
|
|
209
162
|
refreshRunning() {
|
|
210
163
|
return this.timer !== undefined;
|
|
211
164
|
}
|
|
212
|
-
/**
|
|
213
|
-
* #322 批1:窗长换代(`costQuotaWindowSec` 热更新)。
|
|
214
|
-
*
|
|
215
|
-
* 🔴 如实语义:窗长是**桶键的一部分**(`floor(now/windowMs)`),不是行里的一个可改字段。换窗长 ⇒ 当代
|
|
216
|
-
* 桶重算 ⇒ 本副本对「当前窗已用多少」的读数从新桶起算(旧桶的行还在库里,按它自己的窗长归属)。
|
|
217
|
-
* 未 flush 的本地增量仍带着**它自己的**桶键(rowKey 里已含),所以它们仍落回原来那个桶,不会被搬家。
|
|
218
|
-
* 这与限额值的换代不同(那个是纯比较参数,窗内计数一个不动),因此单独成注:运营改窗长要知道自己在
|
|
219
|
-
* 换的是**记账周期**,不是一个阈值。
|
|
220
|
-
*/
|
|
221
165
|
setWindowMs(windowMs) {
|
|
222
166
|
this.windowMs = windowMs;
|
|
223
167
|
}
|
|
224
168
|
}
|
|
225
|
-
/** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
|
|
226
169
|
export class WriteBehindCounter extends SqlWriteBehindCounter {
|
|
227
170
|
constructor(pool, t, windowMs, now, onDegraded, queryTimeoutMs) {
|
|
228
171
|
super("tidb", pool, t, windowMs, now, onDegraded, queryTimeoutMs);
|
|
229
172
|
}
|
|
230
173
|
}
|
|
231
|
-
/** PostgreSQL binding — the class pg-rate-limiter.ts used to define inline (and pg-cost-quota.ts duplicated
|
|
232
|
-
* privately, hardwired to the cost_quota columns). Same ctor shape as the mysql2 twin. */
|
|
233
174
|
export class PgWriteBehindCounter extends SqlWriteBehindCounter {
|
|
234
175
|
constructor(pool, t, windowMs, now, onDegraded, queryTimeoutMs) {
|
|
235
176
|
super("pg", pool, t, windowMs, now, onDegraded, queryTimeoutMs);
|
package/dist/principal-jwt.js
CHANGED
|
@@ -1,29 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/80 D-G: principal crypto-binding (the direct-door IDENTITY gate) + the combined direct-door proof
|
|
3
|
-
* verifier. Split out of `security.ts` (design/158 A13; consumers import from HERE — [2354] cleared the security.ts compat re-exports;
|
|
4
|
-
* `verifyDirectDoorProof` imports {@link verifyApprovalHmac} from `./approval-hmac.js` for the
|
|
5
|
-
* integrity half of the combined proof; `security.ts`'s own `verifiedPrincipal` imports {@link verifyPrincipalJwt}
|
|
6
|
-
* from here for the direct-door identity branch).
|
|
7
|
-
*
|
|
8
|
-
* ✅ WIRED (was "🔴 SCAFFOLD — NOT YET WIRED" — STALE, corrected by the 2026-07-25 doc-rot sweep). Two live call
|
|
9
|
-
* sites, both traced: `verifiedPrincipal` (security.ts) takes the direct-door branch when `directDoorActive` and
|
|
10
|
-
* derives the principal from {@link verifyPrincipalJwt} instead of the header, and {@link verifyDirectDoorProof}
|
|
11
|
-
* verifies the same JWT for the decide routes. Still true, and the part worth keeping: `directDoorActive` is
|
|
12
|
-
* computed from the anchors (config.ts) and is OFF by default, so the direct branch never runs in a stock
|
|
13
|
-
* deployment — activation = provisioning the issuer JWKS + iss/aud, a deployment task, not a coding one.
|
|
14
|
-
* The direct-connect /decide door must NEVER trust a client-supplied principal header
|
|
15
|
-
* (invariant #1). Instead the client presents a SIGNED JWT (sub = principal), and the worker verifies the SIGNATURE
|
|
16
|
-
* against the issuer's PUBLIC key — so identity is crypto-DERIVED, not header-asserted. PUBLIC keys ⇒ non-secret
|
|
17
|
-
* (ride the config projection like OPERATOR_PRINCIPALS, not the HMAC secret path). The issuer's PRIVATE key lives
|
|
18
|
-
* ONLY with the trusted minter (the BFF/identity service), never on a worker. HMAC stays integrity; this is identity.
|
|
19
|
-
*/
|
|
20
1
|
import { createHash, createPublicKey, verify as cryptoVerify } from "node:crypto";
|
|
21
2
|
import { verifyApprovalHmac } from "./approval-hmac.js";
|
|
22
3
|
function b64urlToBuf(s) {
|
|
23
4
|
return Buffer.from(s, "base64url");
|
|
24
5
|
}
|
|
25
|
-
/** Map a PINNED JWS alg → the node:crypto verify call. EdDSA(Ed25519): algorithm=null. RS256: RSA-PKCS1+sha256.
|
|
26
|
-
* ES256: ECDSA P-256 with the JWS raw r||s encoding (dsaEncoding ieee-p1363, NOT DER). Returns false on mismatch. */
|
|
27
6
|
function verifyJwsSignature(alg, signingInput, sig, pub) {
|
|
28
7
|
try {
|
|
29
8
|
if (alg === "EdDSA")
|
|
@@ -38,12 +17,6 @@ function verifyJwsSignature(alg, signingInput, sig, pub) {
|
|
|
38
17
|
return false;
|
|
39
18
|
}
|
|
40
19
|
}
|
|
41
|
-
/**
|
|
42
|
-
* design/80 D-G: verify a compact JWS principal token. Returns the crypto-verified principal (`sub`) + the
|
|
43
|
-
* channel-binding (`bnd`) on success, or `null` on ANY failure (caller fail-closes — NEVER falls back to a header).
|
|
44
|
-
* 🔴 Hardening: the JWS header `alg` MUST equal the kid's PINNED alg (rejects `alg:none`, symmetric, downgrade);
|
|
45
|
-
* the kid MUST resolve to a configured key; iss/aud/exp/nbf (with a small skew) are enforced; `sub` MUST be present.
|
|
46
|
-
*/
|
|
47
20
|
export function verifyPrincipalJwt(token, keys, expect) {
|
|
48
21
|
if (keys.length === 0)
|
|
49
22
|
return null;
|
|
@@ -62,15 +35,13 @@ export function verifyPrincipalJwt(token, keys, expect) {
|
|
|
62
35
|
catch {
|
|
63
36
|
return null;
|
|
64
37
|
}
|
|
65
|
-
// A payload of the JSON literal `null` (or any non-object) parses WITHOUT throwing — guard before any deref so a
|
|
66
|
-
// crafted `base64url("null").<payload>.<sig>` token fail-closes to a typed reject, not an uncaught TypeError → 500.
|
|
67
38
|
if (typeof header !== "object" || header === null || typeof payload !== "object" || payload === null)
|
|
68
39
|
return null;
|
|
69
40
|
if (typeof header.alg !== "string" || typeof header.kid !== "string" || sig.length === 0)
|
|
70
41
|
return null;
|
|
71
42
|
const key = keys.find((k) => k.kid === header.kid);
|
|
72
43
|
if (!key || key.alg !== header.alg)
|
|
73
|
-
return null;
|
|
44
|
+
return null;
|
|
74
45
|
let pub;
|
|
75
46
|
try {
|
|
76
47
|
pub = createPublicKey(key.key);
|
|
@@ -81,7 +52,6 @@ export function verifyPrincipalJwt(token, keys, expect) {
|
|
|
81
52
|
const signingInput = Buffer.from(`${h}.${p}`, "utf8");
|
|
82
53
|
if (!verifyJwsSignature(key.alg, signingInput, sig, pub))
|
|
83
54
|
return null;
|
|
84
|
-
// claims — signature verified, now enforce the envelope
|
|
85
55
|
if (payload.iss !== expect.iss)
|
|
86
56
|
return null;
|
|
87
57
|
const aud = payload.aud;
|
|
@@ -92,57 +62,32 @@ export function verifyPrincipalJwt(token, keys, expect) {
|
|
|
92
62
|
return null;
|
|
93
63
|
const now = expect.nowSec ?? Math.floor(Date.now() / 1000);
|
|
94
64
|
const skew = expect.skewSec ?? 30;
|
|
95
|
-
// 🔴 exp is MANDATORY (the design mandates a short-lived token, ≤120s) — a missing/non-numeric exp must FAIL, else a
|
|
96
|
-
// token minted without exp would never expire (anti-replay = single-use checkpointToken ONLY, with no time bound).
|
|
97
65
|
if (typeof payload.exp !== "number" || payload.exp + skew < now)
|
|
98
|
-
return null;
|
|
66
|
+
return null;
|
|
99
67
|
if (payload.nbf !== undefined && (typeof payload.nbf !== "number" || payload.nbf - skew > now))
|
|
100
|
-
return null;
|
|
101
|
-
// Cap the TTL so a sloppy/over-permissive minter can't widen the replay window past the short-lived mandate.
|
|
68
|
+
return null;
|
|
102
69
|
if (expect.maxTtlSec && payload.exp - now > expect.maxTtlSec + skew)
|
|
103
70
|
return null;
|
|
104
71
|
const bnd = typeof payload.cnf?.bnd === "string" ? payload.cnf.bnd : undefined;
|
|
105
72
|
return { sub: payload.sub, bnd };
|
|
106
73
|
}
|
|
107
|
-
/** The channel-binding value a direct client's principal JWT must carry in `cnf.bnd`: the hex SHA-256 of the
|
|
108
|
-
* CLIENT-VISIBLE action identity (sessionId + boundCallId + boundInputHash — NOT the unexposed checkpointToken).
|
|
109
|
-
* Binds the identity assertion to THIS specific suspended action so a captured JWT cannot be lifted onto another. */
|
|
110
74
|
export function approvalBnd(env) {
|
|
111
75
|
return createHash("sha256").update(JSON.stringify([env.sessionId, env.boundCallId ?? null, env.boundInputHash ?? null])).digest("hex");
|
|
112
76
|
}
|
|
113
|
-
/**
|
|
114
|
-
* design/80 D-G: verify the WHOLE direct-door proof for one /decide — the signed principal JWT (identity) + its
|
|
115
|
-
* cnf.bnd channel-binding + the HMAC envelope (integrity) — and return the CRYPTO-VERIFIED principal, or a typed
|
|
116
|
-
* reject. 🔴 Fail-closed: every failure rejects (the caller must NOT fall back to the trusted header). Order:
|
|
117
|
-
* verify principal → bind → mac-present → mac-valid (so a non-operator never gets a MAC oracle, an unverified
|
|
118
|
-
* caller never reaches the operator list).
|
|
119
|
-
*
|
|
120
|
-
* 🔴 `opts.actionBinding`: the boundCallId/boundInputHash action-binding is `policy_ask`-
|
|
121
|
-
* SPECIFIC (a pending TOOL call). A NON-ACTION gate — `plan_review` (and any review-pause) — `binds NO action`
|
|
122
|
-
* (core checkpoint-store.ts: pendingAction `{kind:"plan_review"}`, editedPlan is a typed sibling that binds no
|
|
123
|
-
* action), so the proof correctly DEGRADES to "verify WHO": JWT identity + cnf.bnd channel-binding + the HMAC over
|
|
124
|
-
* {sessionId, decision} (bound fields null) — WITHOUT requiring a boundInputHash. Pass `actionBinding:false` for a
|
|
125
|
-
* non-action gate; the default (true) keeps the /decide tool-approval contract byte-identical. Binding the decision
|
|
126
|
-
* to a plan HASH was REJECTED (the plan artifact lives in a profile ref store; the worker has no TOCTOU original).
|
|
127
|
-
*/
|
|
128
77
|
export function verifyDirectDoorProof(proof, envelope, config, opts) {
|
|
129
|
-
const actionBinding = opts?.actionBinding ?? true;
|
|
78
|
+
const actionBinding = opts?.actionBinding ?? true;
|
|
130
79
|
if (!proof.jwt)
|
|
131
80
|
return { ok: false, status: 401, errorCode: "principal_unverified", error: "direct door requires a signed principal token (X-Approval-Principal-Token)" };
|
|
132
81
|
const verified = verifyPrincipalJwt(proof.jwt, config.principalJwtPubkeys, { iss: config.principalJwtIss ?? "\0", aud: config.principalJwtAud ?? "\0", maxTtlSec: config.principalJwtMaxTtlSec });
|
|
133
82
|
if (!verified)
|
|
134
83
|
return { ok: false, status: 401, errorCode: "principal_unverified", error: "principal token failed verification" };
|
|
135
|
-
// action-binding gates (policy_ask) MUST carry the bound action; a non-action gate (plan_review) skips this — the
|
|
136
|
-
// cnf.bnd + HMAC below still bind {sessionId, decision} (with null bound fields), so WHO + decision-integrity hold.
|
|
137
84
|
if (actionBinding && !envelope.boundInputHash)
|
|
138
85
|
return { ok: false, status: 400, errorCode: "approval_mac_required", error: "direct door requires boundInputHash (the bound action) in the body" };
|
|
139
86
|
if (verified.bnd !== approvalBnd(envelope))
|
|
140
87
|
return { ok: false, status: 401, errorCode: "principal_unbound", error: "principal token is not bound to this decision (cnf.bnd mismatch)" };
|
|
141
88
|
if (!proof.mac)
|
|
142
89
|
return { ok: false, status: 401, errorCode: "approval_mac_required", error: "direct door requires an approval MAC (X-Approval-Mac)" };
|
|
143
|
-
const ok = verifyApprovalHmac(
|
|
144
|
-
// reason 与两个绑定位同样归一到 null(缺席=null),使"服务端算的"与"签名器签的"在缺席形上逐字节一致。
|
|
145
|
-
{ sessionId: envelope.sessionId, boundCallId: envelope.boundCallId ?? null, boundInputHash: envelope.boundInputHash ?? null, decision: envelope.decision, reason: envelope.reason ?? null }, proof.mac, proof.kid, config.approvalHmacKeys);
|
|
90
|
+
const ok = verifyApprovalHmac({ sessionId: envelope.sessionId, boundCallId: envelope.boundCallId ?? null, boundInputHash: envelope.boundInputHash ?? null, decision: envelope.decision, reason: envelope.reason ?? null }, proof.mac, proof.kid, config.approvalHmacKeys);
|
|
146
91
|
if (!ok)
|
|
147
92
|
return { ok: false, status: 401, errorCode: "approval_mac_invalid", error: "approval envelope MAC invalid" };
|
|
148
93
|
return { ok: true, principal: verified.sub };
|