@evomap/evolver-core 2.0.0-beta.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/dist/algo/antiDistill.d.ts +35 -0
- package/dist/algo/antiDistill.js +167 -0
- package/dist/algo/bans.d.ts +6 -0
- package/dist/algo/bans.js +33 -0
- package/dist/algo/candidateAssembly.d.ts +65 -0
- package/dist/algo/candidateAssembly.js +196 -0
- package/dist/algo/capabilityCandidates.d.ts +38 -0
- package/dist/algo/capabilityCandidates.js +131 -0
- package/dist/algo/confidence.d.ts +106 -0
- package/dist/algo/confidence.js +196 -0
- package/dist/algo/cycleEngine.d.ts +143 -0
- package/dist/algo/cycleEngine.js +417 -0
- package/dist/algo/cycleFailureClassifier.d.ts +52 -0
- package/dist/algo/cycleFailureClassifier.js +104 -0
- package/dist/algo/epigenetics.d.ts +14 -0
- package/dist/algo/epigenetics.js +41 -0
- package/dist/algo/evolutionEvent.d.ts +23 -0
- package/dist/algo/evolutionEvent.js +24 -0
- package/dist/algo/exploration.d.ts +34 -0
- package/dist/algo/exploration.js +55 -0
- package/dist/algo/geneHealth.d.ts +24 -0
- package/dist/algo/geneHealth.js +16 -0
- package/dist/algo/geneIntake.d.ts +37 -0
- package/dist/algo/geneIntake.js +100 -0
- package/dist/algo/genePromotion.d.ts +48 -0
- package/dist/algo/genePromotion.js +53 -0
- package/dist/algo/geneSelection.d.ts +128 -0
- package/dist/algo/geneSelection.js +222 -0
- package/dist/algo/index.d.ts +18 -0
- package/dist/algo/index.js +18 -0
- package/dist/algo/mutation.d.ts +12 -0
- package/dist/algo/mutation.js +22 -0
- package/dist/algo/orchestrator.d.ts +65 -0
- package/dist/algo/orchestrator.js +63 -0
- package/dist/algo/solidify.d.ts +43 -0
- package/dist/algo/solidify.js +63 -0
- package/dist/algo/strategyPresets.d.ts +32 -0
- package/dist/algo/strategyPresets.js +61 -0
- package/dist/assetstore/index.d.ts +8 -0
- package/dist/assetstore/index.js +8 -0
- package/dist/assetstore/learningHistory.d.ts +25 -0
- package/dist/assetstore/learningHistory.js +38 -0
- package/dist/assetstore/localJsonl.d.ts +34 -0
- package/dist/assetstore/localJsonl.js +182 -0
- package/dist/assetstore/pendingSignals.d.ts +22 -0
- package/dist/assetstore/pendingSignals.js +171 -0
- package/dist/assetstore/provenance.d.ts +39 -0
- package/dist/assetstore/provenance.js +109 -0
- package/dist/assetstore/provider.d.ts +48 -0
- package/dist/assetstore/provider.js +36 -0
- package/dist/assetstore/remoteStub.d.ts +32 -0
- package/dist/assetstore/remoteStub.js +36 -0
- package/dist/assetstore/reviewFilter.d.ts +20 -0
- package/dist/assetstore/reviewFilter.js +44 -0
- package/dist/assetstore/reviewLedger.d.ts +55 -0
- package/dist/assetstore/reviewLedger.js +121 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +79 -0
- package/dist/benchmark/antiGeneBenchmark.js +241 -0
- package/dist/benchmark/antiGeneRollout.d.ts +80 -0
- package/dist/benchmark/antiGeneRollout.js +256 -0
- package/dist/benchmark/benchmark.d.ts +32 -0
- package/dist/benchmark/benchmark.js +26 -0
- package/dist/benchmark/evolutionThesisSolver.d.ts +25 -0
- package/dist/benchmark/evolutionThesisSolver.js +41 -0
- package/dist/benchmark/index.d.ts +5 -0
- package/dist/benchmark/index.js +5 -0
- package/dist/benchmark/thesis.d.ts +85 -0
- package/dist/benchmark/thesis.js +150 -0
- package/dist/bootstrap/envFingerprint.d.ts +45 -0
- package/dist/bootstrap/envFingerprint.js +72 -0
- package/dist/bootstrap/index.d.ts +2 -0
- package/dist/bootstrap/index.js +2 -0
- package/dist/bootstrap/selfEvolve.d.ts +36 -0
- package/dist/bootstrap/selfEvolve.js +26 -0
- package/dist/cycle/cycleTimeline.d.ts +35 -0
- package/dist/cycle/cycleTimeline.js +74 -0
- package/dist/cycle/index.d.ts +2 -0
- package/dist/cycle/index.js +2 -0
- package/dist/cycle/stateMachine.d.ts +6 -0
- package/dist/cycle/stateMachine.js +25 -0
- package/dist/daemon/cycleContext.d.ts +12 -0
- package/dist/daemon/cycleContext.js +13 -0
- package/dist/daemon/drain.d.ts +11 -0
- package/dist/daemon/drain.js +16 -0
- package/dist/daemon/guards.d.ts +87 -0
- package/dist/daemon/guards.js +142 -0
- package/dist/daemon/idempotency.d.ts +11 -0
- package/dist/daemon/idempotency.js +36 -0
- package/dist/daemon/idleScheduler.d.ts +55 -0
- package/dist/daemon/idleScheduler.js +181 -0
- package/dist/daemon/index.d.ts +8 -0
- package/dist/daemon/index.js +8 -0
- package/dist/daemon/stepRunner.d.ts +12 -0
- package/dist/daemon/stepRunner.js +31 -0
- package/dist/daemon/unhandledRejectionWindow.d.ts +21 -0
- package/dist/daemon/unhandledRejectionWindow.js +52 -0
- package/dist/daemon/watchdog.d.ts +11 -0
- package/dist/daemon/watchdog.js +14 -0
- package/dist/events/eventSchema.d.ts +132 -0
- package/dist/events/eventSchema.js +30 -0
- package/dist/events/eventStore.d.ts +32 -0
- package/dist/events/eventStore.js +137 -0
- package/dist/events/ingest.d.ts +34 -0
- package/dist/events/ingest.js +61 -0
- package/dist/events/paths.d.ts +22 -0
- package/dist/events/paths.js +44 -0
- package/dist/events/projectors.d.ts +9 -0
- package/dist/events/projectors.js +11 -0
- package/dist/events/public.d.ts +14 -0
- package/dist/events/public.js +8 -0
- package/dist/events/replayer.d.ts +26 -0
- package/dist/events/replayer.js +67 -0
- package/dist/events/reports.d.ts +90 -0
- package/dist/events/reports.js +244 -0
- package/dist/events/retention.d.ts +70 -0
- package/dist/events/retention.js +177 -0
- package/dist/events/sink.d.ts +5 -0
- package/dist/events/sink.js +1 -0
- package/dist/exec/autoExec.d.ts +137 -0
- package/dist/exec/autoExec.js +221 -0
- package/dist/exec/autonomousCycle.d.ts +48 -0
- package/dist/exec/autonomousCycle.js +78 -0
- package/dist/exec/claudeBridge.d.ts +112 -0
- package/dist/exec/claudeBridge.js +219 -0
- package/dist/exec/index.d.ts +9 -0
- package/dist/exec/index.js +11 -0
- package/dist/exec/openPrRegistry.d.ts +60 -0
- package/dist/exec/openPrRegistry.js +130 -0
- package/dist/exec/policy/blastRadius.d.ts +31 -0
- package/dist/exec/policy/blastRadius.js +67 -0
- package/dist/exec/policy/constraints.d.ts +26 -0
- package/dist/exec/policy/constraints.js +62 -0
- package/dist/exec/policy/destructive.d.ts +45 -0
- package/dist/exec/policy/destructive.js +108 -0
- package/dist/exec/policy/failureMode.d.ts +33 -0
- package/dist/exec/policy/failureMode.js +49 -0
- package/dist/exec/policy/index.d.ts +24 -0
- package/dist/exec/policy/index.js +30 -0
- package/dist/exec/policy/protectedPaths.d.ts +17 -0
- package/dist/exec/policy/protectedPaths.js +65 -0
- package/dist/exec/policyCheck.d.ts +2 -0
- package/dist/exec/policyCheck.js +7 -0
- package/dist/exec/prompt.d.ts +34 -0
- package/dist/exec/prompt.js +108 -0
- package/dist/exec/proofOfWork.d.ts +9 -0
- package/dist/exec/proofOfWork.js +11 -0
- package/dist/exec/runnerRegistry.d.ts +123 -0
- package/dist/exec/runnerRegistry.js +237 -0
- package/dist/exec/selfPr.d.ts +83 -0
- package/dist/exec/selfPr.js +104 -0
- package/dist/exec/selfPrObfuscation.d.ts +21 -0
- package/dist/exec/selfPrObfuscation.js +85 -0
- package/dist/hooks/hooks.d.ts +117 -0
- package/dist/hooks/hooks.js +89 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hub/assetCallLog.d.ts +43 -0
- package/dist/hub/assetCallLog.js +72 -0
- package/dist/hub/bindings.d.ts +28 -0
- package/dist/hub/bindings.js +90 -0
- package/dist/hub/capability.d.ts +278 -0
- package/dist/hub/capability.js +1 -0
- package/dist/hub/conversationDistiller.d.ts +88 -0
- package/dist/hub/conversationDistiller.js +264 -0
- package/dist/hub/fake.d.ts +45 -0
- package/dist/hub/fake.js +69 -0
- package/dist/hub/hubReview.d.ts +91 -0
- package/dist/hub/hubReview.js +106 -0
- package/dist/hub/index.d.ts +10 -0
- package/dist/hub/index.js +10 -0
- package/dist/hub/ingest.d.ts +15 -0
- package/dist/hub/ingest.js +15 -0
- package/dist/hub/questionGenerator.d.ts +59 -0
- package/dist/hub/questionGenerator.js +406 -0
- package/dist/hub/reuseDecision.d.ts +87 -0
- package/dist/hub/reuseDecision.js +127 -0
- package/dist/hub/sanitize.d.ts +52 -0
- package/dist/hub/sanitize.js +321 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/mailbox/catalog.d.ts +17 -0
- package/dist/mailbox/catalog.js +53 -0
- package/dist/mailbox/daemon.d.ts +48 -0
- package/dist/mailbox/daemon.js +61 -0
- package/dist/mailbox/dispatch.d.ts +46 -0
- package/dist/mailbox/dispatch.js +62 -0
- package/dist/mailbox/envelope.d.ts +43 -0
- package/dist/mailbox/envelope.js +52 -0
- package/dist/mailbox/index.d.ts +8 -0
- package/dist/mailbox/index.js +8 -0
- package/dist/mailbox/ipcServer.d.ts +46 -0
- package/dist/mailbox/ipcServer.js +168 -0
- package/dist/mailbox/requestReply.d.ts +40 -0
- package/dist/mailbox/requestReply.js +77 -0
- package/dist/mailbox/store.d.ts +74 -0
- package/dist/mailbox/store.js +308 -0
- package/dist/mailbox/wake.d.ts +45 -0
- package/dist/mailbox/wake.js +60 -0
- package/dist/material/boundary.d.ts +7 -0
- package/dist/material/boundary.js +14 -0
- package/dist/material/consumer.d.ts +21 -0
- package/dist/material/consumer.js +52 -0
- package/dist/material/emit.d.ts +55 -0
- package/dist/material/emit.js +52 -0
- package/dist/material/factory.d.ts +27 -0
- package/dist/material/factory.js +25 -0
- package/dist/material/index.d.ts +8 -0
- package/dist/material/index.js +8 -0
- package/dist/material/materialStore.d.ts +24 -0
- package/dist/material/materialStore.js +76 -0
- package/dist/material/sampling.d.ts +21 -0
- package/dist/material/sampling.js +39 -0
- package/dist/material/sources.d.ts +25 -0
- package/dist/material/sources.js +33 -0
- package/dist/material/watermark.d.ts +21 -0
- package/dist/material/watermark.js +76 -0
- package/dist/observers/cursorRewriteObserver.d.ts +46 -0
- package/dist/observers/cursorRewriteObserver.js +103 -0
- package/dist/observers/distillObserver.d.ts +31 -0
- package/dist/observers/distillObserver.js +93 -0
- package/dist/observers/index.d.ts +5 -0
- package/dist/observers/index.js +5 -0
- package/dist/observers/observerBus.d.ts +44 -0
- package/dist/observers/observerBus.js +73 -0
- package/dist/observers/reflectionObserver.d.ts +31 -0
- package/dist/observers/reflectionObserver.js +124 -0
- package/dist/observers/valueDigestObserver.d.ts +50 -0
- package/dist/observers/valueDigestObserver.js +70 -0
- package/dist/ops/cleanup.d.ts +23 -0
- package/dist/ops/cleanup.js +68 -0
- package/dist/ops/health.d.ts +37 -0
- package/dist/ops/health.js +96 -0
- package/dist/ops/index.d.ts +7 -0
- package/dist/ops/index.js +7 -0
- package/dist/ops/recall.d.ts +65 -0
- package/dist/ops/recall.js +152 -0
- package/dist/ops/reuseOutcomes.d.ts +68 -0
- package/dist/ops/reuseOutcomes.js +126 -0
- package/dist/ops/savingsCore.d.ts +47 -0
- package/dist/ops/savingsCore.js +112 -0
- package/dist/ops/selfUpdate.d.ts +106 -0
- package/dist/ops/selfUpdate.js +294 -0
- package/dist/ops/valueLedger.d.ts +205 -0
- package/dist/ops/valueLedger.js +238 -0
- package/dist/ops/valueOutreach.d.ts +70 -0
- package/dist/ops/valueOutreach.js +244 -0
- package/dist/personality/drift.d.ts +45 -0
- package/dist/personality/drift.js +106 -0
- package/dist/personality/events.d.ts +25 -0
- package/dist/personality/events.js +37 -0
- package/dist/personality/evolveOps.d.ts +20 -0
- package/dist/personality/evolveOps.js +93 -0
- package/dist/personality/index.d.ts +11 -0
- package/dist/personality/index.js +11 -0
- package/dist/personality/mutate.d.ts +21 -0
- package/dist/personality/mutate.js +39 -0
- package/dist/personality/pivot.d.ts +23 -0
- package/dist/personality/pivot.js +22 -0
- package/dist/personality/prompt.d.ts +6 -0
- package/dist/personality/prompt.js +62 -0
- package/dist/personality/riskGate.d.ts +42 -0
- package/dist/personality/riskGate.js +80 -0
- package/dist/personality/schema.d.ts +255 -0
- package/dist/personality/schema.js +119 -0
- package/dist/personality/select.d.ts +46 -0
- package/dist/personality/select.js +71 -0
- package/dist/personality/stats.d.ts +46 -0
- package/dist/personality/stats.js +82 -0
- package/dist/personality/store.d.ts +35 -0
- package/dist/personality/store.js +92 -0
- package/dist/schema/common.d.ts +28 -0
- package/dist/schema/common.js +17 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/material.d.ts +201 -0
- package/dist/schema/material.js +50 -0
- package/dist/schema/problem.d.ts +160 -0
- package/dist/schema/problem.js +45 -0
- package/dist/schema/proofOfWork.d.ts +97 -0
- package/dist/schema/proofOfWork.js +9 -0
- package/dist/schema/signal.d.ts +41 -0
- package/dist/schema/signal.js +19 -0
- package/dist/shadow/index.d.ts +6 -0
- package/dist/shadow/index.js +6 -0
- package/dist/shadow/jsonlSink.d.ts +17 -0
- package/dist/shadow/jsonlSink.js +27 -0
- package/dist/shadow/redact.d.ts +11 -0
- package/dist/shadow/redact.js +18 -0
- package/dist/shadow/shadowHub.d.ts +12 -0
- package/dist/shadow/shadowHub.js +42 -0
- package/dist/shadow/shadowMailbox.d.ts +17 -0
- package/dist/shadow/shadowMailbox.js +25 -0
- package/dist/shadow/shadowStore.d.ts +18 -0
- package/dist/shadow/shadowStore.js +55 -0
- package/dist/shadow/sink.d.ts +37 -0
- package/dist/shadow/sink.js +18 -0
- package/dist/signals/cycleHistoryFromEvents.d.ts +11 -0
- package/dist/signals/cycleHistoryFromEvents.js +96 -0
- package/dist/signals/expand.d.ts +34 -0
- package/dist/signals/expand.js +125 -0
- package/dist/signals/extractor.d.ts +25 -0
- package/dist/signals/extractor.js +57 -0
- package/dist/signals/index.d.ts +6 -0
- package/dist/signals/index.js +6 -0
- package/dist/signals/metaSignals.d.ts +57 -0
- package/dist/signals/metaSignals.js +158 -0
- package/dist/signals/signalGate.d.ts +15 -0
- package/dist/signals/signalGate.js +40 -0
- package/dist/signals/traceSignals.d.ts +44 -0
- package/dist/signals/traceSignals.js +123 -0
- package/dist/signatures/classifier.d.ts +16 -0
- package/dist/signatures/classifier.js +29 -0
- package/dist/signatures/index.d.ts +2 -0
- package/dist/signatures/index.js +2 -0
- package/dist/signatures/signatures.d.ts +22 -0
- package/dist/signatures/signatures.js +42 -0
- package/dist/strategy/experiment.d.ts +48 -0
- package/dist/strategy/experiment.js +63 -0
- package/dist/strategy/index.d.ts +2 -0
- package/dist/strategy/index.js +2 -0
- package/dist/strategy/strategyPoint.d.ts +30 -0
- package/dist/strategy/strategyPoint.js +23 -0
- package/dist/trace/index.d.ts +2 -0
- package/dist/trace/index.js +2 -0
- package/dist/trace/trajectory.d.ts +82 -0
- package/dist/trace/trajectory.js +117 -0
- package/dist/trace/trajectoryExport.d.ts +237 -0
- package/dist/trace/trajectoryExport.js +1818 -0
- package/dist/trigger/budget.d.ts +16 -0
- package/dist/trigger/budget.js +20 -0
- package/dist/trigger/engine.d.ts +12 -0
- package/dist/trigger/engine.js +23 -0
- package/dist/trigger/index.d.ts +6 -0
- package/dist/trigger/index.js +6 -0
- package/dist/trigger/problemBuilder.d.ts +24 -0
- package/dist/trigger/problemBuilder.js +32 -0
- package/dist/trigger/trigger.d.ts +22 -0
- package/dist/trigger/trigger.js +27 -0
- package/dist/trigger/valueModel.d.ts +26 -0
- package/dist/trigger/valueModel.js +25 -0
- package/dist/trigger/wfq.d.ts +13 -0
- package/dist/trigger/wfq.js +25 -0
- package/dist/util/fileLock.d.ts +24 -0
- package/dist/util/fileLock.js +227 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +2 -0
- package/dist/util/version.d.ts +12 -0
- package/dist/util/version.js +51 -0
- package/dist/verify/index.d.ts +4 -0
- package/dist/verify/index.js +4 -0
- package/dist/verify/rollback.d.ts +22 -0
- package/dist/verify/rollback.js +33 -0
- package/dist/verify/sandboxRunner.d.ts +39 -0
- package/dist/verify/sandboxRunner.js +116 -0
- package/dist/verify/sandboxedValidation.d.ts +35 -0
- package/dist/verify/sandboxedValidation.js +56 -0
- package/dist/verify/validation.d.ts +41 -0
- package/dist/verify/validation.js +85 -0
- package/dist/wire/canonicalize.golden.d.ts +23 -0
- package/dist/wire/canonicalize.golden.js +46 -0
- package/dist/wire/geneHints.d.ts +64 -0
- package/dist/wire/geneHints.js +100 -0
- package/dist/wire/index.d.ts +91 -0
- package/dist/wire/index.js +3 -0
- package/dist/wire/schemaGate.d.ts +11 -0
- package/dist/wire/schemaGate.js +49 -0
- package/dist/workflow/dsl.d.ts +54 -0
- package/dist/workflow/dsl.js +15 -0
- package/dist/workflow/engine.d.ts +30 -0
- package/dist/workflow/engine.js +94 -0
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.js +2 -0
- package/package.json +29 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RootEvent } from '../events/eventSchema.js';
|
|
2
|
+
import type { EventSink } from '../events/sink.js';
|
|
3
|
+
export interface ObserverMeta {
|
|
4
|
+
name: string;
|
|
5
|
+
eventTypes?: readonly string[];
|
|
6
|
+
idempotent: boolean;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Observer {
|
|
10
|
+
readonly meta: ObserverMeta;
|
|
11
|
+
handle(event: Readonly<RootEvent>): void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface DlqEntry {
|
|
14
|
+
observer: string;
|
|
15
|
+
event: Readonly<RootEvent>;
|
|
16
|
+
error: string;
|
|
17
|
+
at: string;
|
|
18
|
+
reason: 'throw' | 'timeout';
|
|
19
|
+
}
|
|
20
|
+
export declare class InvalidObserverMetaError extends Error {
|
|
21
|
+
constructor(msg: string);
|
|
22
|
+
}
|
|
23
|
+
/** 进程内 observer bus (军杰§8): meta 强制 + quarantine + DLQ; 旁路, 故障绝不影响主写路径. */
|
|
24
|
+
export declare class ObserverBus implements EventSink {
|
|
25
|
+
private readonly observers;
|
|
26
|
+
private readonly quarantined;
|
|
27
|
+
private readonly dlq;
|
|
28
|
+
private readonly failCounts;
|
|
29
|
+
private inflight;
|
|
30
|
+
private readonly poisonThreshold;
|
|
31
|
+
constructor(opts?: {
|
|
32
|
+
poisonThreshold?: number;
|
|
33
|
+
});
|
|
34
|
+
register(observer: Observer): void;
|
|
35
|
+
/** fan-out: 非阻塞, observer 故障隔离, 绝不向调用方抛. */
|
|
36
|
+
dispatch(event: RootEvent): void;
|
|
37
|
+
private run;
|
|
38
|
+
private withTimeout;
|
|
39
|
+
/** 等所有 inflight 完成 (优雅关闭/测试). */
|
|
40
|
+
drain(): Promise<void>;
|
|
41
|
+
isQuarantined(name: string): boolean;
|
|
42
|
+
reset(name: string): void;
|
|
43
|
+
deadLetters(): readonly DlqEntry[];
|
|
44
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export class InvalidObserverMetaError extends Error {
|
|
2
|
+
constructor(msg) { super(`observer meta 非法: ${msg}`); this.name = 'InvalidObserverMetaError'; }
|
|
3
|
+
}
|
|
4
|
+
function deepFreeze(o) {
|
|
5
|
+
if (o && typeof o === 'object') {
|
|
6
|
+
for (const v of Object.values(o))
|
|
7
|
+
deepFreeze(v);
|
|
8
|
+
Object.freeze(o);
|
|
9
|
+
}
|
|
10
|
+
return o;
|
|
11
|
+
}
|
|
12
|
+
/** 进程内 observer bus (军杰§8): meta 强制 + quarantine + DLQ; 旁路, 故障绝不影响主写路径. */
|
|
13
|
+
export class ObserverBus {
|
|
14
|
+
observers = new Map();
|
|
15
|
+
quarantined = new Set();
|
|
16
|
+
dlq = [];
|
|
17
|
+
failCounts = new Map();
|
|
18
|
+
inflight = [];
|
|
19
|
+
poisonThreshold;
|
|
20
|
+
constructor(opts = {}) { this.poisonThreshold = opts.poisonThreshold ?? 1; }
|
|
21
|
+
register(observer) {
|
|
22
|
+
const m = observer.meta;
|
|
23
|
+
if (!m || typeof m.name !== 'string' || m.name.length === 0)
|
|
24
|
+
throw new InvalidObserverMetaError('name 必填');
|
|
25
|
+
if (typeof m.idempotent !== 'boolean')
|
|
26
|
+
throw new InvalidObserverMetaError('idempotent 必填(boolean)');
|
|
27
|
+
if (typeof m.timeoutMs !== 'number' || m.timeoutMs <= 0)
|
|
28
|
+
throw new InvalidObserverMetaError('timeoutMs 必填(>0)');
|
|
29
|
+
if (this.observers.has(m.name))
|
|
30
|
+
throw new InvalidObserverMetaError(`重名 observer: ${m.name}`);
|
|
31
|
+
this.observers.set(m.name, observer);
|
|
32
|
+
}
|
|
33
|
+
/** fan-out: 非阻塞, observer 故障隔离, 绝不向调用方抛. */
|
|
34
|
+
dispatch(event) {
|
|
35
|
+
const frozen = deepFreeze(structuredClone(event));
|
|
36
|
+
for (const obs of this.observers.values()) {
|
|
37
|
+
if (this.quarantined.has(obs.meta.name))
|
|
38
|
+
continue;
|
|
39
|
+
if (obs.meta.eventTypes && !obs.meta.eventTypes.includes(event.type))
|
|
40
|
+
continue;
|
|
41
|
+
this.inflight.push(this.run(obs, frozen));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async run(obs, event) {
|
|
45
|
+
try {
|
|
46
|
+
await this.withTimeout(Promise.resolve(obs.handle(event)), obs.meta.timeoutMs, obs.meta.name);
|
|
47
|
+
this.failCounts.delete(obs.meta.name);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
const reason = e._timeout ? 'timeout' : 'throw';
|
|
51
|
+
this.dlq.push({ observer: obs.meta.name, event, error: e.message, at: new Date().toISOString(), reason });
|
|
52
|
+
const n = (this.failCounts.get(obs.meta.name) ?? 0) + 1;
|
|
53
|
+
this.failCounts.set(obs.meta.name, n);
|
|
54
|
+
if (n >= this.poisonThreshold || reason === 'timeout')
|
|
55
|
+
this.quarantined.add(obs.meta.name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
withTimeout(p, ms, name) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const t = setTimeout(() => {
|
|
61
|
+
const err = new Error(`observer ${name} 超时 ${ms}ms`);
|
|
62
|
+
err._timeout = true;
|
|
63
|
+
reject(err);
|
|
64
|
+
}, ms);
|
|
65
|
+
p.then((v) => { clearTimeout(t); resolve(v); }, (e) => { clearTimeout(t); reject(e instanceof Error ? e : new Error(String(e))); });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** 等所有 inflight 完成 (优雅关闭/测试). */
|
|
69
|
+
async drain() { const cur = this.inflight; this.inflight = []; await Promise.allSettled(cur); }
|
|
70
|
+
isQuarantined(name) { return this.quarantined.has(name); }
|
|
71
|
+
reset(name) { this.quarantined.delete(name); this.failCounts.delete(name); }
|
|
72
|
+
deadLetters() { return this.dlq; }
|
|
73
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Ingestor } from '../events/ingest.js';
|
|
2
|
+
import type { Observer } from './observerBus.js';
|
|
3
|
+
export declare const REFLECTION_RECORDED_EVENT_TYPE = "reflection.recorded";
|
|
4
|
+
export declare const REFLECTION_SOURCE_EVENT_TYPES: readonly ["cycle.solidified", "cycle.failed"];
|
|
5
|
+
export type ReflectionOutcome = 'success' | 'failed' | 'inert';
|
|
6
|
+
export type ReflectionAction = 'reinforce_successful_pattern' | 'investigate_failure_pattern' | 'avoid_inert_loop';
|
|
7
|
+
export interface ReflectionSummaryInput {
|
|
8
|
+
type: string;
|
|
9
|
+
sourceEventId: string;
|
|
10
|
+
cycleId: string;
|
|
11
|
+
geneId?: string;
|
|
12
|
+
outcome?: ReflectionOutcome;
|
|
13
|
+
score?: number;
|
|
14
|
+
producedValue?: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ReflectionSummary {
|
|
18
|
+
sourceEventId: string;
|
|
19
|
+
cycleId: string;
|
|
20
|
+
outcome: ReflectionOutcome;
|
|
21
|
+
action: ReflectionAction;
|
|
22
|
+
summary: string;
|
|
23
|
+
geneId?: string;
|
|
24
|
+
score?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface ReflectionObserverDeps {
|
|
27
|
+
ingestor: Ingestor;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function buildReflectionSummary(input: ReflectionSummaryInput): ReflectionSummary;
|
|
31
|
+
export declare function reflectionObserver(deps: ReflectionObserverDeps): Observer;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export const REFLECTION_RECORDED_EVENT_TYPE = 'reflection.recorded';
|
|
2
|
+
export const REFLECTION_SOURCE_EVENT_TYPES = ['cycle.solidified', 'cycle.failed'];
|
|
3
|
+
export function buildReflectionSummary(input) {
|
|
4
|
+
const outcome = normalizeOutcome(input);
|
|
5
|
+
const action = actionFor(outcome);
|
|
6
|
+
const genePart = input.geneId ? ` using ${input.geneId}` : '';
|
|
7
|
+
const scorePart = typeof input.score === 'number' ? ` score=${trimScore(input.score)}` : '';
|
|
8
|
+
const errorPart = input.error ? ` Error: ${clip(input.error, 80)}` : '';
|
|
9
|
+
const summary = clip(outcome === 'success'
|
|
10
|
+
? `Cycle ${input.cycleId} succeeded${genePart}${scorePart}; keep this pattern available for similar signals.`
|
|
11
|
+
: outcome === 'inert'
|
|
12
|
+
? `Cycle ${input.cycleId} produced no value${genePart}${scorePart}; avoid reinforcing an inert loop.`
|
|
13
|
+
: `Cycle ${input.cycleId} failed${genePart}${scorePart}; inspect the failure before reusing this pattern.${errorPart}`, 220);
|
|
14
|
+
return cleanUndefined({ sourceEventId: input.sourceEventId, cycleId: input.cycleId, outcome, action, summary, geneId: input.geneId, score: input.score });
|
|
15
|
+
}
|
|
16
|
+
export function reflectionObserver(deps) {
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const inFlight = new Map();
|
|
19
|
+
const meta = {
|
|
20
|
+
name: 'reflection',
|
|
21
|
+
eventTypes: REFLECTION_SOURCE_EVENT_TYPES,
|
|
22
|
+
idempotent: true,
|
|
23
|
+
timeoutMs: deps.timeoutMs ?? 5_000,
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
meta,
|
|
27
|
+
async handle(event) {
|
|
28
|
+
const input = reflectionInputFromEvent(event);
|
|
29
|
+
if (!input)
|
|
30
|
+
return;
|
|
31
|
+
if (seen.has(input.sourceEventId))
|
|
32
|
+
return;
|
|
33
|
+
const pending = inFlight.get(input.sourceEventId);
|
|
34
|
+
if (pending) {
|
|
35
|
+
await pending;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const recording = Promise.resolve()
|
|
39
|
+
.then(async () => {
|
|
40
|
+
const reflection = buildReflectionSummary(input);
|
|
41
|
+
await deps.ingestor.ingest({
|
|
42
|
+
type: REFLECTION_RECORDED_EVENT_TYPE,
|
|
43
|
+
human: {
|
|
44
|
+
title: `reflection ${reflection.cycleId}`,
|
|
45
|
+
detail: reflection.summary,
|
|
46
|
+
why: 'deterministic post-cycle reflection observer',
|
|
47
|
+
},
|
|
48
|
+
payload: reflection,
|
|
49
|
+
});
|
|
50
|
+
seen.add(input.sourceEventId);
|
|
51
|
+
})
|
|
52
|
+
.finally(() => {
|
|
53
|
+
inFlight.delete(input.sourceEventId);
|
|
54
|
+
});
|
|
55
|
+
inFlight.set(input.sourceEventId, recording);
|
|
56
|
+
await recording;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function reflectionInputFromEvent(event) {
|
|
61
|
+
if (!REFLECTION_SOURCE_EVENT_TYPES.includes(event.type))
|
|
62
|
+
return null;
|
|
63
|
+
const payload = asRecord(event.payload);
|
|
64
|
+
const cycleId = stringOf(payload?.['cycleId']);
|
|
65
|
+
if (!cycleId)
|
|
66
|
+
return null;
|
|
67
|
+
const outcomeRecord = asRecord(payload?.['outcome']);
|
|
68
|
+
const status = stringOf(outcomeRecord?.['status']);
|
|
69
|
+
const outcome = status === 'success' ? 'success' : status === 'failed' ? 'failed' : undefined;
|
|
70
|
+
const producedValue = boolOf(payload?.['producedValue']);
|
|
71
|
+
const error = stringOf(payload?.['error'])
|
|
72
|
+
?? stringOf(payload?.['reason'])
|
|
73
|
+
?? stringOf(outcomeRecord?.['reason'])
|
|
74
|
+
?? stringOf(payload?.['failure_class_reason']);
|
|
75
|
+
return cleanUndefined({
|
|
76
|
+
type: event.type,
|
|
77
|
+
sourceEventId: event.eventId,
|
|
78
|
+
cycleId,
|
|
79
|
+
geneId: stringOf(payload?.['gene']),
|
|
80
|
+
outcome,
|
|
81
|
+
score: numberOf(outcomeRecord?.['score']),
|
|
82
|
+
producedValue,
|
|
83
|
+
error,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function normalizeOutcome(input) {
|
|
87
|
+
if (input.type === 'cycle.failed')
|
|
88
|
+
return 'failed';
|
|
89
|
+
if (input.producedValue === false)
|
|
90
|
+
return 'inert';
|
|
91
|
+
return input.outcome ?? 'success';
|
|
92
|
+
}
|
|
93
|
+
function actionFor(outcome) {
|
|
94
|
+
if (outcome === 'success')
|
|
95
|
+
return 'reinforce_successful_pattern';
|
|
96
|
+
if (outcome === 'inert')
|
|
97
|
+
return 'avoid_inert_loop';
|
|
98
|
+
return 'investigate_failure_pattern';
|
|
99
|
+
}
|
|
100
|
+
function clip(value, max) {
|
|
101
|
+
return value.length <= max ? value : `${value.slice(0, max - 3)}...`;
|
|
102
|
+
}
|
|
103
|
+
function trimScore(value) {
|
|
104
|
+
return Math.round(value * 1000) / 1000;
|
|
105
|
+
}
|
|
106
|
+
function asRecord(value) {
|
|
107
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
108
|
+
}
|
|
109
|
+
function stringOf(value) {
|
|
110
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
|
|
111
|
+
}
|
|
112
|
+
function numberOf(value) {
|
|
113
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
114
|
+
}
|
|
115
|
+
function boolOf(value) {
|
|
116
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
117
|
+
}
|
|
118
|
+
function cleanUndefined(record) {
|
|
119
|
+
for (const key of Object.keys(record)) {
|
|
120
|
+
if (record[key] === undefined)
|
|
121
|
+
delete record[key];
|
|
122
|
+
}
|
|
123
|
+
return record;
|
|
124
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Observer } from './observerBus.js';
|
|
2
|
+
import type { ValueSummary, SummaryWindow } from '../ops/valueLedger.js';
|
|
3
|
+
/** A delivery channel for a built digest. Local file + terminal MOTD to start; webhook/desktop later. */
|
|
4
|
+
export interface DigestSink {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
deliver(markdown: string, meta: {
|
|
7
|
+
period: string;
|
|
8
|
+
at: string;
|
|
9
|
+
}): void | Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** Persists the last delivery time so the cadence survives restarts (the bus is in-process; weeks are not). */
|
|
12
|
+
export interface DigestStateStore {
|
|
13
|
+
/** Epoch ms of the last delivery, or undefined if never delivered. */
|
|
14
|
+
lastDeliveredAt(): number | undefined;
|
|
15
|
+
/** Record a delivery at epoch ms. */
|
|
16
|
+
markDelivered(at: number): void;
|
|
17
|
+
}
|
|
18
|
+
/** In-memory state store (tests / ephemeral runs). Production injects a file-backed one. */
|
|
19
|
+
export declare class InMemoryDigestState implements DigestStateStore {
|
|
20
|
+
private last;
|
|
21
|
+
constructor(seed?: number);
|
|
22
|
+
lastDeliveredAt(): number | undefined;
|
|
23
|
+
markDelivered(at: number): void;
|
|
24
|
+
}
|
|
25
|
+
export declare const DEFAULT_DIGEST_CADENCE_MS: number;
|
|
26
|
+
export interface ValueDigestObserverDeps {
|
|
27
|
+
/** Aggregate the value summary for a window (composition wires this to loadValueSummary over trace+events). */
|
|
28
|
+
summaryProvider(window: SummaryWindow): ValueSummary;
|
|
29
|
+
/** Where a built digest goes. */
|
|
30
|
+
sink: DigestSink;
|
|
31
|
+
/** Cadence state (last-delivery persistence). Default in-memory (no persistence across process restarts). */
|
|
32
|
+
state?: DigestStateStore;
|
|
33
|
+
/** Injected clock (epoch ms). Default Date.now. */
|
|
34
|
+
now?: () => number;
|
|
35
|
+
/** Minimum interval between deliveries. Default weekly. */
|
|
36
|
+
cadenceMs?: number;
|
|
37
|
+
/** Observer handle timeout (the bus quarantines on timeout). Default 5s — plenty for format + a local sink. */
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build the value-digest observer. Subscribes to ALL events (it is cadence-driven, not event-type-driven: any
|
|
42
|
+
* event is just a "tick" that prompts the due-check). On each tick:
|
|
43
|
+
* 1. cadence gate — skip unless `cadenceMs` has elapsed since the last delivery;
|
|
44
|
+
* 2. value gate — aggregate the past `cadenceMs` window and skip unless it has MEASURED value (digestShouldSend);
|
|
45
|
+
* 3. deliver — build the markdown digest and hand it to the sink, then record the delivery time.
|
|
46
|
+
* The handler swallows nothing on its own — it lets the bus's fault isolation catch a throwing sink (so a broken
|
|
47
|
+
* sink quarantines the observer without touching the main write path). idempotent=false: a delivery has an
|
|
48
|
+
* external side effect (a file write / a printed MOTD), so the bus must not assume it can re-run it freely.
|
|
49
|
+
*/
|
|
50
|
+
export declare function valueDigestObserver(deps: ValueDigestObserverDeps): Observer;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Value digest observer (#113) — the observer bus's FIRST built-in observer. The bus has shipped (fault
|
|
2
|
+
// isolation + DLQ + timeout) with zero members; this is the first notification side-effect hung off it.
|
|
3
|
+
//
|
|
4
|
+
// What it does: on each dispatched event it asks "is a weekly digest due?" — and only when (a) at least the
|
|
5
|
+
// cadence has elapsed since the last delivery AND (b) this period produced MEASURED value does it build a digest
|
|
6
|
+
// and hand it to a pluggable sink. Everything else is a no-op. The cadence + the "measured value only" gate are
|
|
7
|
+
// the frequency controls the issue makes first-class ("宁可少触达,不可惹人烦").
|
|
8
|
+
//
|
|
9
|
+
// Boundaries: the observer NEVER derives savings itself — a `summaryProvider` is injected (the composition layer
|
|
10
|
+
// wires it to loadValueSummary over trace+events). The sink is injected too (file / MOTD / future webhook). The
|
|
11
|
+
// observer only orchestrates: cadence check → gate → format → deliver → record delivery. All side-effecting
|
|
12
|
+
// dependencies (clock, last-delivery state, sink) are injected so it is fully testable without real I/O.
|
|
13
|
+
import { buildValueDigest, digestShouldSend } from '../ops/valueOutreach.js';
|
|
14
|
+
/** In-memory state store (tests / ephemeral runs). Production injects a file-backed one. */
|
|
15
|
+
export class InMemoryDigestState {
|
|
16
|
+
last;
|
|
17
|
+
constructor(seed) { this.last = seed; }
|
|
18
|
+
lastDeliveredAt() { return this.last; }
|
|
19
|
+
markDelivered(at) { this.last = at; }
|
|
20
|
+
}
|
|
21
|
+
export const DEFAULT_DIGEST_CADENCE_MS = 7 * 24 * 60 * 60 * 1000; // weekly
|
|
22
|
+
/** Period label "YYYY-MM-DD..YYYY-MM-DD" for the window [since, now). Deterministic given the two epochs. */
|
|
23
|
+
function periodLabel(sinceMs, nowMs) {
|
|
24
|
+
const d = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
25
|
+
return `${d(sinceMs)}..${d(nowMs)}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the value-digest observer. Subscribes to ALL events (it is cadence-driven, not event-type-driven: any
|
|
29
|
+
* event is just a "tick" that prompts the due-check). On each tick:
|
|
30
|
+
* 1. cadence gate — skip unless `cadenceMs` has elapsed since the last delivery;
|
|
31
|
+
* 2. value gate — aggregate the past `cadenceMs` window and skip unless it has MEASURED value (digestShouldSend);
|
|
32
|
+
* 3. deliver — build the markdown digest and hand it to the sink, then record the delivery time.
|
|
33
|
+
* The handler swallows nothing on its own — it lets the bus's fault isolation catch a throwing sink (so a broken
|
|
34
|
+
* sink quarantines the observer without touching the main write path). idempotent=false: a delivery has an
|
|
35
|
+
* external side effect (a file write / a printed MOTD), so the bus must not assume it can re-run it freely.
|
|
36
|
+
*/
|
|
37
|
+
export function valueDigestObserver(deps) {
|
|
38
|
+
const state = deps.state ?? new InMemoryDigestState();
|
|
39
|
+
const now = deps.now ?? (() => Date.now());
|
|
40
|
+
const cadenceMs = deps.cadenceMs ?? DEFAULT_DIGEST_CADENCE_MS;
|
|
41
|
+
const meta = {
|
|
42
|
+
name: 'value-digest',
|
|
43
|
+
idempotent: false,
|
|
44
|
+
timeoutMs: deps.timeoutMs ?? 5_000,
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
meta,
|
|
48
|
+
async handle() {
|
|
49
|
+
const t = now();
|
|
50
|
+
const last = state.lastDeliveredAt();
|
|
51
|
+
// 1. cadence gate.
|
|
52
|
+
if (last !== undefined && t - last < cadenceMs)
|
|
53
|
+
return;
|
|
54
|
+
// 2. value gate — only a window WITH measured value is worth an unsolicited push.
|
|
55
|
+
const since = t - cadenceMs;
|
|
56
|
+
const window = { since: new Date(since).toISOString(), until: new Date(t).toISOString() };
|
|
57
|
+
const summary = deps.summaryProvider(window);
|
|
58
|
+
if (!digestShouldSend(summary))
|
|
59
|
+
return; // zero-measured-value week → produce nothing
|
|
60
|
+
const period = periodLabel(since, t);
|
|
61
|
+
const markdown = buildValueDigest(summary, period);
|
|
62
|
+
if (markdown === null)
|
|
63
|
+
return; // belt-and-suspenders: gate already passed, but never deliver a null
|
|
64
|
+
// 3. deliver, then record the delivery time so the cadence advances. If the sink throws, the bus isolates
|
|
65
|
+
// it (DLQ + quarantine) and the delivery time is NOT advanced, so a fixed sink retries next tick.
|
|
66
|
+
await deps.sink.deliver(markdown, { period, at: new Date(t).toISOString() });
|
|
67
|
+
state.markDelivered(t);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CleanupPolicy {
|
|
2
|
+
/** Delete files older than this (ms), beyond the minKeep newest. Default 7 days. */
|
|
3
|
+
maxAgeMs?: number;
|
|
4
|
+
/** Always keep at least this many newest files regardless of age. Default 10. */
|
|
5
|
+
minKeep?: number;
|
|
6
|
+
/** Hard cap: keep at most this many files total (newest wins). Default 50. */
|
|
7
|
+
maxFiles?: number;
|
|
8
|
+
/** Clock (test seam). Default: Date.now. */
|
|
9
|
+
now?: () => number;
|
|
10
|
+
}
|
|
11
|
+
export interface CleanupResult {
|
|
12
|
+
scanned: number;
|
|
13
|
+
deleted: number;
|
|
14
|
+
deletedPaths: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Reclaim stale artifacts matching `pattern` in `dir`. Returns what was scanned/deleted (for audit/logging).
|
|
18
|
+
* Two phases: age-based (keep the minKeep newest, delete the rest older than maxAgeMs), then a maxFiles count
|
|
19
|
+
* cap. A non-existent dir → a clean zero result. Never throws.
|
|
20
|
+
*/
|
|
21
|
+
export declare function cleanupArtifacts(dir: string, pattern: RegExp, policy?: CleanupPolicy): CleanupResult;
|
|
22
|
+
/** Convenience: GC the exec bridge's leftover patch files (`evolver-patch-*.diff`) in a temp dir. */
|
|
23
|
+
export declare function cleanupExecPatches(tmpDir: string, policy?: CleanupPolicy): CleanupResult;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Artifact GC (ported + generalized from v1 src/ops/cleanup.js). A resident daemon accumulates artifacts —
|
|
2
|
+
// exec patch files (proof.gitDiff.patchRef under tmpdir), old event logs, scratch prompts — that nothing ever
|
|
3
|
+
// reclaims. This applies a two-phase retention policy to a directory: (1) age-based, always keeping the N
|
|
4
|
+
// newest regardless of age; (2) a hard count cap as a safety backstop. Generic over a filename pattern so the
|
|
5
|
+
// daemon can GC each artifact kind. Best-effort + graceful: a missing dir or an undeletable file never throws.
|
|
6
|
+
import { existsSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
function listMatching(dir, pattern) {
|
|
9
|
+
return readdirSync(dir)
|
|
10
|
+
.filter((f) => pattern.test(f))
|
|
11
|
+
.map((f) => {
|
|
12
|
+
const path = join(dir, f);
|
|
13
|
+
try {
|
|
14
|
+
return { path, mtimeMs: statSync(path).mtimeMs };
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { path, mtimeMs: 0 };
|
|
18
|
+
} // raced/removed — sorts oldest, deletion is a no-op
|
|
19
|
+
})
|
|
20
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs); // newest first
|
|
21
|
+
}
|
|
22
|
+
function safeBatchDelete(paths) {
|
|
23
|
+
const deleted = [];
|
|
24
|
+
for (const p of paths) {
|
|
25
|
+
try {
|
|
26
|
+
unlinkSync(p);
|
|
27
|
+
deleted.push(p);
|
|
28
|
+
}
|
|
29
|
+
catch { /* best-effort */ }
|
|
30
|
+
}
|
|
31
|
+
return deleted;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reclaim stale artifacts matching `pattern` in `dir`. Returns what was scanned/deleted (for audit/logging).
|
|
35
|
+
* Two phases: age-based (keep the minKeep newest, delete the rest older than maxAgeMs), then a maxFiles count
|
|
36
|
+
* cap. A non-existent dir → a clean zero result. Never throws.
|
|
37
|
+
*/
|
|
38
|
+
export function cleanupArtifacts(dir, pattern, policy = {}) {
|
|
39
|
+
const maxAgeMs = policy.maxAgeMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
40
|
+
const minKeep = policy.minKeep ?? 10;
|
|
41
|
+
const maxFiles = policy.maxFiles ?? 50;
|
|
42
|
+
const now = policy.now ?? (() => Date.now());
|
|
43
|
+
if (!existsSync(dir))
|
|
44
|
+
return { scanned: 0, deleted: 0, deletedPaths: [] };
|
|
45
|
+
let files;
|
|
46
|
+
try {
|
|
47
|
+
files = listMatching(dir, pattern);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { scanned: 0, deleted: 0, deletedPaths: [] };
|
|
51
|
+
}
|
|
52
|
+
const scanned = files.length;
|
|
53
|
+
const t = now();
|
|
54
|
+
const deletedPaths = [];
|
|
55
|
+
// Phase 1: age-based, but always keep the minKeep newest.
|
|
56
|
+
const ageVictims = files.slice(minKeep).filter((e) => t - e.mtimeMs > maxAgeMs).map((e) => e.path);
|
|
57
|
+
deletedPaths.push(...safeBatchDelete(ageVictims));
|
|
58
|
+
// Phase 2: hard count cap (newest wins) over whatever remains.
|
|
59
|
+
const remaining = files.filter((e) => !deletedPaths.includes(e.path));
|
|
60
|
+
if (remaining.length > maxFiles) {
|
|
61
|
+
deletedPaths.push(...safeBatchDelete(remaining.slice(maxFiles).map((e) => e.path)));
|
|
62
|
+
}
|
|
63
|
+
return { scanned, deleted: deletedPaths.length, deletedPaths };
|
|
64
|
+
}
|
|
65
|
+
/** Convenience: GC the exec bridge's leftover patch files (`evolver-patch-*.diff`) in a temp dir. */
|
|
66
|
+
export function cleanupExecPatches(tmpDir, policy) {
|
|
67
|
+
return cleanupArtifacts(tmpDir, /^evolver-patch-.*\.diff$/, policy);
|
|
68
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type Severity = 'info' | 'warning' | 'critical';
|
|
2
|
+
export interface HealthCheck {
|
|
3
|
+
name: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
status: string;
|
|
6
|
+
severity?: Severity;
|
|
7
|
+
}
|
|
8
|
+
export interface HealthReport {
|
|
9
|
+
status: 'ok' | 'warning' | 'error';
|
|
10
|
+
timestamp: string;
|
|
11
|
+
checks: HealthCheck[];
|
|
12
|
+
}
|
|
13
|
+
export interface HealthThresholds {
|
|
14
|
+
/** disk % used → warning (default 80) / critical (default 90). */
|
|
15
|
+
diskWarnPct: number;
|
|
16
|
+
diskCritPct: number;
|
|
17
|
+
/** memory % used → critical (default 95). */
|
|
18
|
+
memCritPct: number;
|
|
19
|
+
/** process count → warning (default 2000; possible fork bomb / leak). */
|
|
20
|
+
procWarn: number;
|
|
21
|
+
}
|
|
22
|
+
export interface HealthProbes {
|
|
23
|
+
/** Disk % used (0-100), or -1 if unknown. Default: statfs of the cwd's root. */
|
|
24
|
+
diskPctUsed?: () => number;
|
|
25
|
+
/** Memory % used (0-100). Default: os.freemem/totalmem. */
|
|
26
|
+
memPctUsed?: () => number;
|
|
27
|
+
/** Live process count, or -1 if unavailable on this platform. Default: count of /proc/<pid> (Linux only). */
|
|
28
|
+
procCount?: () => number;
|
|
29
|
+
/** Clock (test seam). Default: real time. */
|
|
30
|
+
now?: () => Date;
|
|
31
|
+
thresholds?: Partial<HealthThresholds>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Snapshot host health for the daemon supervisor. Returns the overall status (ok | warning | error) plus a
|
|
35
|
+
* per-check breakdown. Pure given its probes — inject HealthProbes to test each branch deterministically.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runHealthCheck(probes?: HealthProbes): HealthReport;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Resident-daemon health check (ported from v1 src/ops/health_check.js). A long-running autonomous loop should
|
|
2
|
+
// know when its host is unhealthy — out of disk (it writes patches/event logs), memory-pressured, or leaking
|
|
3
|
+
// processes (a hung-agent fork bomb) — so the supervisor can pause or alert rather than crash mid-cycle. The
|
|
4
|
+
// resource probes are injected seams (default = real OS readings) so the policy is deterministic to test.
|
|
5
|
+
import { statfsSync } from 'node:fs';
|
|
6
|
+
import { readdirSync } from 'node:fs';
|
|
7
|
+
import { freemem, totalmem } from 'node:os';
|
|
8
|
+
import { parse as parsePath } from 'node:path';
|
|
9
|
+
const DEFAULT_THRESHOLDS = { diskWarnPct: 80, diskCritPct: 90, memCritPct: 95, procWarn: 2000 };
|
|
10
|
+
function defaultDiskPctUsed() {
|
|
11
|
+
try {
|
|
12
|
+
const root = process.platform === 'win32' ? (parsePath(process.cwd()).root || 'C:\\') : '/';
|
|
13
|
+
const s = statfsSync(root);
|
|
14
|
+
const total = Number(s.blocks) * Number(s.bsize);
|
|
15
|
+
const free = Number(s.bavail) * Number(s.bsize); // available to unprivileged users
|
|
16
|
+
if (!(total > 0))
|
|
17
|
+
return -1;
|
|
18
|
+
return Math.round(((total - free) / total) * 100);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return -1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function defaultMemPctUsed() {
|
|
25
|
+
const total = totalmem();
|
|
26
|
+
if (!(total > 0))
|
|
27
|
+
return -1;
|
|
28
|
+
return Math.round(((total - freemem()) / total) * 100);
|
|
29
|
+
}
|
|
30
|
+
let procCache = -1;
|
|
31
|
+
let procCacheAt = 0;
|
|
32
|
+
function defaultProcCount(now) {
|
|
33
|
+
if (process.platform !== 'linux')
|
|
34
|
+
return -1;
|
|
35
|
+
try {
|
|
36
|
+
const t = now().getTime();
|
|
37
|
+
if (procCache < 0 || t - procCacheAt > 60_000) { // readdir('/proc') is heavy — cache 60s
|
|
38
|
+
procCache = readdirSync('/proc').filter((f) => /^\d+$/.test(f)).length;
|
|
39
|
+
procCacheAt = t;
|
|
40
|
+
}
|
|
41
|
+
return procCache;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Snapshot host health for the daemon supervisor. Returns the overall status (ok | warning | error) plus a
|
|
49
|
+
* per-check breakdown. Pure given its probes — inject HealthProbes to test each branch deterministically.
|
|
50
|
+
*/
|
|
51
|
+
export function runHealthCheck(probes = {}) {
|
|
52
|
+
const now = probes.now ?? (() => new Date());
|
|
53
|
+
const th = { ...DEFAULT_THRESHOLDS, ...(probes.thresholds ?? {}) };
|
|
54
|
+
const diskPctUsed = probes.diskPctUsed ?? defaultDiskPctUsed;
|
|
55
|
+
const memPctUsed = probes.memPctUsed ?? defaultMemPctUsed;
|
|
56
|
+
const procCount = probes.procCount ?? (() => defaultProcCount(now));
|
|
57
|
+
const checks = [];
|
|
58
|
+
let critical = 0;
|
|
59
|
+
let warnings = 0;
|
|
60
|
+
const disk = diskPctUsed();
|
|
61
|
+
if (disk < 0) {
|
|
62
|
+
checks.push({ name: 'disk_space', ok: false, status: 'unavailable', severity: 'warning' });
|
|
63
|
+
warnings++;
|
|
64
|
+
}
|
|
65
|
+
else if (disk > th.diskCritPct) {
|
|
66
|
+
checks.push({ name: 'disk_space', ok: false, status: `${disk}% used`, severity: 'critical' });
|
|
67
|
+
critical++;
|
|
68
|
+
}
|
|
69
|
+
else if (disk > th.diskWarnPct) {
|
|
70
|
+
checks.push({ name: 'disk_space', ok: false, status: `${disk}% used`, severity: 'warning' });
|
|
71
|
+
warnings++;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
checks.push({ name: 'disk_space', ok: true, status: `${disk}% used` });
|
|
75
|
+
}
|
|
76
|
+
const mem = memPctUsed();
|
|
77
|
+
if (mem >= 0 && mem > th.memCritPct) {
|
|
78
|
+
checks.push({ name: 'memory', ok: false, status: `${mem}% used`, severity: 'critical' });
|
|
79
|
+
critical++;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
checks.push({ name: 'memory', ok: true, status: `${mem < 0 ? 'unknown' : mem + '% used'}` });
|
|
83
|
+
}
|
|
84
|
+
const procs = procCount();
|
|
85
|
+
if (procs >= 0) {
|
|
86
|
+
if (procs > th.procWarn) {
|
|
87
|
+
checks.push({ name: 'process_count', ok: false, status: `${procs} procs`, severity: 'warning' });
|
|
88
|
+
warnings++;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
checks.push({ name: 'process_count', ok: true, status: `${procs} procs` });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const status = critical > 0 ? 'error' : warnings > 0 ? 'warning' : 'ok';
|
|
95
|
+
return { status, timestamp: now().toISOString(), checks };
|
|
96
|
+
}
|