@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,8 @@
|
|
|
1
|
+
export * from './provider.js';
|
|
2
|
+
export * from './localJsonl.js';
|
|
3
|
+
export * from './remoteStub.js';
|
|
4
|
+
export * from './learningHistory.js';
|
|
5
|
+
export * from './provenance.js';
|
|
6
|
+
export * from './reviewLedger.js';
|
|
7
|
+
export * from './reviewFilter.js';
|
|
8
|
+
export * from './pendingSignals.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ProofOfWork } from '../schema/proofOfWork.js';
|
|
2
|
+
import type { AssetStoreProvider } from './provider.js';
|
|
3
|
+
/**
|
|
4
|
+
* Gene 侧 learning_history 聚合视图(M3-4 后半 / M3-6). 批注#28: 不再把 learning_history 内联进 Gene 资产
|
|
5
|
+
* (会改 asset_id 不稳), 而是按需从 Capsule 集合聚合派生.
|
|
6
|
+
*/
|
|
7
|
+
export interface GeneLearningView {
|
|
8
|
+
geneId: string;
|
|
9
|
+
total: number;
|
|
10
|
+
success: number;
|
|
11
|
+
failed: number;
|
|
12
|
+
/**
|
|
13
|
+
* Inert (zero-work) capsules: `success` outcome with no measurable produced value (#195). Tallied apart from
|
|
14
|
+
* real `success` so a do-nothing gene's successRate is not inflated. Optional for back-compat with hand-built
|
|
15
|
+
* views (absent ⇒ 0). When derived here, success + failed + inert === total.
|
|
16
|
+
*/
|
|
17
|
+
inert?: number;
|
|
18
|
+
successRate: number;
|
|
19
|
+
avgScore: number;
|
|
20
|
+
recentCapsuleIds: string[];
|
|
21
|
+
}
|
|
22
|
+
/** 扫该 gene 关联的 Capsule, 派生学习视图(只读, 不落库). */
|
|
23
|
+
export declare function aggregateLearningHistory(provider: AssetStoreProvider, geneId: string, recentN?: number): Promise<GeneLearningView>;
|
|
24
|
+
/** M3-5: 校验 agent 提交的 proof_of_work(zod). 返回规范化值或抛. */
|
|
25
|
+
export declare function validateProofOfWork(input: unknown): ProofOfWork;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { proofOfWork } from '../schema/proofOfWork.js';
|
|
2
|
+
import { proofIndicatesOutput } from '../algo/solidify.js';
|
|
3
|
+
/** 扫该 gene 关联的 Capsule, 派生学习视图(只读, 不落库). */
|
|
4
|
+
export async function aggregateLearningHistory(provider, geneId, recentN = 10) {
|
|
5
|
+
const caps = await provider.search({ kind: 'Capsule', gene: geneId, limit: 10_000 });
|
|
6
|
+
let success = 0, failed = 0, inert = 0, scoreSum = 0;
|
|
7
|
+
for (const c of caps) {
|
|
8
|
+
const outcome = c.outcome;
|
|
9
|
+
if (outcome?.status === 'failed') {
|
|
10
|
+
failed += 1;
|
|
11
|
+
}
|
|
12
|
+
else if (outcome?.status === 'success') {
|
|
13
|
+
// #195: a success that produced no measurable value (no ProofOfWork output — the same `producedValue`
|
|
14
|
+
// signal solidify uses to mark it `suppressed_observationally`) is INERT, not a real success. Counting it
|
|
15
|
+
// would inflate successRate for a gene that only ever does nothing. Inert is neutral, not a failure: it is
|
|
16
|
+
// excluded from BOTH sides of successRate, so it neither builds nor erodes confidence.
|
|
17
|
+
const proof = c.proof_of_work;
|
|
18
|
+
if (proofIndicatesOutput(proof))
|
|
19
|
+
success += 1;
|
|
20
|
+
else
|
|
21
|
+
inert += 1;
|
|
22
|
+
}
|
|
23
|
+
if (typeof outcome?.score === 'number')
|
|
24
|
+
scoreSum += outcome.score;
|
|
25
|
+
}
|
|
26
|
+
const total = caps.length;
|
|
27
|
+
const decisive = success + failed; // inert excluded from the success-rate denominator (neutral)
|
|
28
|
+
return {
|
|
29
|
+
geneId, total, success, failed, inert,
|
|
30
|
+
successRate: decisive > 0 ? success / decisive : 0,
|
|
31
|
+
avgScore: total > 0 ? scoreSum / total : 0,
|
|
32
|
+
recentCapsuleIds: caps.slice(-recentN).map((c) => c.asset_id),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** M3-5: 校验 agent 提交的 proof_of_work(zod). 返回规范化值或抛. */
|
|
36
|
+
export function validateProofOfWork(input) {
|
|
37
|
+
return proofOfWork.parse(input);
|
|
38
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type AssetKind, type AssetRecord, type AssetStoreProvider, type PutResult, type SearchQuery } from './provider.js';
|
|
2
|
+
/**
|
|
3
|
+
* 本地 jsonl 资产库(M3-2, 移植 v1 src/gep/assetStore.js 单写锁).
|
|
4
|
+
* 每 kind 一文件(genes/capsules/events.jsonl); append-only; O_EXCL 文件锁防并发写撕裂;
|
|
5
|
+
* 内存索引(asset_id→record)供 get/search; 同 asset_id 去重(内容寻址天然幂等).
|
|
6
|
+
*/
|
|
7
|
+
export declare class LocalJsonlProvider implements AssetStoreProvider {
|
|
8
|
+
readonly baseDir: string;
|
|
9
|
+
private readonly index;
|
|
10
|
+
private readonly lockPath;
|
|
11
|
+
private loaded;
|
|
12
|
+
constructor(baseDir: string);
|
|
13
|
+
private ensureLoaded;
|
|
14
|
+
put(asset: AssetRecord): Promise<PutResult>;
|
|
15
|
+
/**
|
|
16
|
+
* 迁移专用(M8-2): 以**冻结 asset_id** 原样写入, 不经 normalizeForPut 重算/校验.
|
|
17
|
+
* 仅 v1→v2 导入用(硬化 A6 存量冻结); 普通写一律走 put(). record 必须自带 asset_id.
|
|
18
|
+
*/
|
|
19
|
+
putFrozen(record: AssetRecord): Promise<PutResult>;
|
|
20
|
+
get(assetId: string): Promise<AssetRecord | null>;
|
|
21
|
+
list(kind?: AssetKind, limit?: number): Promise<AssetRecord[]>;
|
|
22
|
+
search(q: SearchQuery): Promise<AssetRecord[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Opt-in log compaction: rewrite each kind's jsonl keeping ONE line per asset_id (last wins),
|
|
25
|
+
* dropping duplicate/corrupt lines that accumulate across processes/restarts/migration. Lossless
|
|
26
|
+
* because the store is content-addressed (one record per asset_id is all there ever was). This does
|
|
27
|
+
* NOT evict assets — every unique asset is knowledge that stays. Atomic per file (temp + rename) under
|
|
28
|
+
* the write lock. Returns kept/removed line counts.
|
|
29
|
+
*/
|
|
30
|
+
compact(): Promise<{
|
|
31
|
+
kept: number;
|
|
32
|
+
removed: number;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { acquireLock, releaseLock } from '../util/fileLock.js';
|
|
4
|
+
import { normalizeForPut, } from './provider.js';
|
|
5
|
+
const FILES = { Gene: 'genes.jsonl', Capsule: 'capsules.jsonl', EvolutionEvent: 'events.jsonl', AntiGene: 'anti-genes.jsonl' };
|
|
6
|
+
function signalsOf(a) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const key of ['signals_match', 'signals', 'trigger', 'trigger_signals']) {
|
|
9
|
+
const v = a[key];
|
|
10
|
+
if (Array.isArray(v))
|
|
11
|
+
for (const s of v)
|
|
12
|
+
if (typeof s === 'string')
|
|
13
|
+
out.push(s);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 本地 jsonl 资产库(M3-2, 移植 v1 src/gep/assetStore.js 单写锁).
|
|
19
|
+
* 每 kind 一文件(genes/capsules/events.jsonl); append-only; O_EXCL 文件锁防并发写撕裂;
|
|
20
|
+
* 内存索引(asset_id→record)供 get/search; 同 asset_id 去重(内容寻址天然幂等).
|
|
21
|
+
*/
|
|
22
|
+
export class LocalJsonlProvider {
|
|
23
|
+
baseDir;
|
|
24
|
+
index = new Map();
|
|
25
|
+
lockPath;
|
|
26
|
+
loaded = false;
|
|
27
|
+
// `baseDir` is public-readonly so callers that inject a store (e.g. the CLI under test) can co-locate sidecars
|
|
28
|
+
// — the ReviewLedger/ProvenanceStore — in the SAME directory, instead of defaulting to the real ~/.evomap.
|
|
29
|
+
constructor(baseDir) {
|
|
30
|
+
this.baseDir = baseDir;
|
|
31
|
+
mkdirSync(baseDir, { recursive: true });
|
|
32
|
+
this.lockPath = join(baseDir, '.assetstore.lock');
|
|
33
|
+
}
|
|
34
|
+
ensureLoaded() {
|
|
35
|
+
if (this.loaded)
|
|
36
|
+
return;
|
|
37
|
+
for (const file of Object.values(FILES)) {
|
|
38
|
+
const p = join(this.baseDir, file);
|
|
39
|
+
if (!existsSync(p))
|
|
40
|
+
continue;
|
|
41
|
+
for (const line of readFileSync(p, 'utf8').split('\n')) {
|
|
42
|
+
if (!line.trim())
|
|
43
|
+
continue;
|
|
44
|
+
try {
|
|
45
|
+
const r = JSON.parse(line);
|
|
46
|
+
if (r.asset_id)
|
|
47
|
+
this.index.set(r.asset_id, r);
|
|
48
|
+
}
|
|
49
|
+
catch { /* skip 坏行 */ }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.loaded = true;
|
|
53
|
+
}
|
|
54
|
+
async put(asset) {
|
|
55
|
+
this.ensureLoaded();
|
|
56
|
+
const { record, verified } = normalizeForPut(asset);
|
|
57
|
+
if (this.index.has(record.asset_id))
|
|
58
|
+
return { asset_id: record.asset_id, stored: false, verified };
|
|
59
|
+
const file = join(this.baseDir, FILES[record.type]);
|
|
60
|
+
acquireLock(this.lockPath);
|
|
61
|
+
try {
|
|
62
|
+
// 锁内复检(另一进程可能刚写)
|
|
63
|
+
if (!this.index.has(record.asset_id)) {
|
|
64
|
+
appendFileSync(file, `${JSON.stringify(record)}\n`);
|
|
65
|
+
this.index.set(record.asset_id, record);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return { asset_id: record.asset_id, stored: false, verified };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
releaseLock(this.lockPath);
|
|
73
|
+
}
|
|
74
|
+
return { asset_id: record.asset_id, stored: true, verified };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 迁移专用(M8-2): 以**冻结 asset_id** 原样写入, 不经 normalizeForPut 重算/校验.
|
|
78
|
+
* 仅 v1→v2 导入用(硬化 A6 存量冻结); 普通写一律走 put(). record 必须自带 asset_id.
|
|
79
|
+
*/
|
|
80
|
+
async putFrozen(record) {
|
|
81
|
+
this.ensureLoaded();
|
|
82
|
+
if (!record.asset_id)
|
|
83
|
+
throw new Error('putFrozen 需 record 自带冻结 asset_id');
|
|
84
|
+
if (this.index.has(record.asset_id))
|
|
85
|
+
return { asset_id: record.asset_id, stored: false, verified: false };
|
|
86
|
+
const file = join(this.baseDir, FILES[record.type]);
|
|
87
|
+
acquireLock(this.lockPath);
|
|
88
|
+
try {
|
|
89
|
+
if (this.index.has(record.asset_id))
|
|
90
|
+
return { asset_id: record.asset_id, stored: false, verified: false };
|
|
91
|
+
appendFileSync(file, `${JSON.stringify(record)}\n`);
|
|
92
|
+
this.index.set(record.asset_id, record);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
releaseLock(this.lockPath);
|
|
96
|
+
}
|
|
97
|
+
return { asset_id: record.asset_id, stored: true, verified: false };
|
|
98
|
+
}
|
|
99
|
+
async get(assetId) {
|
|
100
|
+
this.ensureLoaded();
|
|
101
|
+
return this.index.get(assetId) ?? null;
|
|
102
|
+
}
|
|
103
|
+
async list(kind, limit = 1000) {
|
|
104
|
+
this.ensureLoaded();
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const r of this.index.values()) {
|
|
107
|
+
if (!kind || r.type === kind)
|
|
108
|
+
out.push(r);
|
|
109
|
+
if (out.length >= limit)
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
async search(q) {
|
|
115
|
+
this.ensureLoaded();
|
|
116
|
+
const out = [];
|
|
117
|
+
for (const r of this.index.values()) {
|
|
118
|
+
if (q.kind && r.type !== q.kind)
|
|
119
|
+
continue;
|
|
120
|
+
if (q.category) {
|
|
121
|
+
const cat = r.category ?? r.intent;
|
|
122
|
+
if (cat !== q.category)
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (q.gene && r.gene !== q.gene)
|
|
126
|
+
continue;
|
|
127
|
+
if (q.signalsAny && q.signalsAny.length > 0) {
|
|
128
|
+
const sig = new Set(signalsOf(r));
|
|
129
|
+
if (!q.signalsAny.some((s) => sig.has(s)))
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (q.text) {
|
|
133
|
+
const summary = String(r.summary ?? '');
|
|
134
|
+
if (!summary.includes(q.text))
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
out.push(r);
|
|
138
|
+
if (out.length >= (q.limit ?? 1000))
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Opt-in log compaction: rewrite each kind's jsonl keeping ONE line per asset_id (last wins),
|
|
145
|
+
* dropping duplicate/corrupt lines that accumulate across processes/restarts/migration. Lossless
|
|
146
|
+
* because the store is content-addressed (one record per asset_id is all there ever was). This does
|
|
147
|
+
* NOT evict assets — every unique asset is knowledge that stays. Atomic per file (temp + rename) under
|
|
148
|
+
* the write lock. Returns kept/removed line counts.
|
|
149
|
+
*/
|
|
150
|
+
async compact() {
|
|
151
|
+
this.ensureLoaded();
|
|
152
|
+
acquireLock(this.lockPath);
|
|
153
|
+
try {
|
|
154
|
+
let kept = 0, removed = 0;
|
|
155
|
+
for (const [kind, file] of Object.entries(FILES)) {
|
|
156
|
+
const p = join(this.baseDir, file);
|
|
157
|
+
if (!existsSync(p))
|
|
158
|
+
continue;
|
|
159
|
+
const lines = readFileSync(p, 'utf8').split('\n').filter((l) => l.trim());
|
|
160
|
+
const byId = new Map(); // asset_id → raw line (last wins, matching load semantics)
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
try {
|
|
163
|
+
const r = JSON.parse(line);
|
|
164
|
+
if (r.asset_id && r.type === kind)
|
|
165
|
+
byId.set(r.asset_id, line);
|
|
166
|
+
}
|
|
167
|
+
catch { /* drop corrupt line */ }
|
|
168
|
+
}
|
|
169
|
+
const out = byId.size ? `${[...byId.values()].join('\n')}\n` : '';
|
|
170
|
+
const tmp = `${p}.compact.tmp`;
|
|
171
|
+
writeFileSync(tmp, out);
|
|
172
|
+
renameSync(tmp, p); // atomic replace; a crash mid-compact leaves the original intact
|
|
173
|
+
kept += byId.size;
|
|
174
|
+
removed += lines.length - byId.size;
|
|
175
|
+
}
|
|
176
|
+
return { kept, removed };
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
releaseLock(this.lockPath);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AssetStoreProvider } from './provider.js';
|
|
2
|
+
export interface PendingSignalsFile {
|
|
3
|
+
signals?: unknown;
|
|
4
|
+
note?: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface PendingSignalsConsumeResult {
|
|
7
|
+
signals: string[];
|
|
8
|
+
path?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PendingSignalsMergeResult {
|
|
11
|
+
signals: string[];
|
|
12
|
+
injected: number;
|
|
13
|
+
path?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface PendingSignalsContext {
|
|
16
|
+
repoRoot?: string;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function pendingSignalsPath(baseDir: string): string;
|
|
20
|
+
export declare function consumePendingSignals(baseDir: string): PendingSignalsConsumeResult;
|
|
21
|
+
export declare function consumePendingSignalsForStore(store: AssetStoreProvider, context?: PendingSignalsContext): PendingSignalsConsumeResult;
|
|
22
|
+
export declare function mergePendingSignalsForStore(store: AssetStoreProvider, baseSignals: readonly string[], context?: PendingSignalsContext): PendingSignalsMergeResult;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { acquireLock, releaseLock } from '../util/fileLock.js';
|
|
4
|
+
import { LocalJsonlProvider } from './localJsonl.js';
|
|
5
|
+
const MAX_SIGNAL_LENGTH = 200;
|
|
6
|
+
export function pendingSignalsPath(baseDir) {
|
|
7
|
+
return join(baseDir, 'pending_signals.json');
|
|
8
|
+
}
|
|
9
|
+
function envPath(name) {
|
|
10
|
+
const raw = process.env[name]?.trim();
|
|
11
|
+
return raw ? raw : null;
|
|
12
|
+
}
|
|
13
|
+
function isDirectory(path) {
|
|
14
|
+
try {
|
|
15
|
+
return statSync(path).isDirectory();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function nearestExistingDir(start) {
|
|
22
|
+
let current = resolve(start);
|
|
23
|
+
while (!existsSync(current)) {
|
|
24
|
+
const parent = dirname(current);
|
|
25
|
+
if (parent === current)
|
|
26
|
+
return null;
|
|
27
|
+
current = parent;
|
|
28
|
+
}
|
|
29
|
+
return isDirectory(current) ? current : dirname(current);
|
|
30
|
+
}
|
|
31
|
+
function findRepoRoot(start) {
|
|
32
|
+
let current = nearestExistingDir(start);
|
|
33
|
+
while (current) {
|
|
34
|
+
if (existsSync(join(current, '.git')))
|
|
35
|
+
return current;
|
|
36
|
+
const parent = dirname(current);
|
|
37
|
+
if (parent === current)
|
|
38
|
+
return null;
|
|
39
|
+
current = parent;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function workspaceRootForRepo(repoRoot) {
|
|
44
|
+
const workspace = join(repoRoot, 'workspace');
|
|
45
|
+
return isDirectory(workspace) ? workspace : repoRoot;
|
|
46
|
+
}
|
|
47
|
+
function workspaceRootFromRepoHint(repoRoot) {
|
|
48
|
+
return workspaceRootForRepo(findRepoRoot(repoRoot) ?? resolve(repoRoot));
|
|
49
|
+
}
|
|
50
|
+
function workspaceRoot(context = {}) {
|
|
51
|
+
if (context.repoRoot)
|
|
52
|
+
return workspaceRootFromRepoHint(context.repoRoot);
|
|
53
|
+
const openclawWorkspace = envPath('OPENCLAW_WORKSPACE');
|
|
54
|
+
if (openclawWorkspace)
|
|
55
|
+
return resolve(openclawWorkspace);
|
|
56
|
+
const explicitRepoRoot = envPath('EVOLVER_REPO_ROOT');
|
|
57
|
+
if (explicitRepoRoot)
|
|
58
|
+
return workspaceRootFromRepoHint(explicitRepoRoot);
|
|
59
|
+
const cwd = context.cwd ?? process.cwd();
|
|
60
|
+
const repoRoot = findRepoRoot(cwd);
|
|
61
|
+
return repoRoot ? workspaceRootForRepo(repoRoot) : resolve(cwd);
|
|
62
|
+
}
|
|
63
|
+
function sessionScope() {
|
|
64
|
+
const raw = String(process.env['EVOLVER_SESSION_SCOPE'] ?? '').trim();
|
|
65
|
+
if (!raw)
|
|
66
|
+
return null;
|
|
67
|
+
const safe = raw.replace(/[^a-zA-Z0-9_\-.]/g, '_').slice(0, 128);
|
|
68
|
+
if (!safe || /^\.{1,2}$/.test(safe) || /\.\./.test(safe))
|
|
69
|
+
return null;
|
|
70
|
+
return safe;
|
|
71
|
+
}
|
|
72
|
+
function legacyV1BaseDir(context = {}, allowEnvOverride = true) {
|
|
73
|
+
const override = allowEnvOverride ? envPath('GEP_ASSETS_DIR') : null;
|
|
74
|
+
const baseDir = override ? resolve(override) : join(workspaceRoot(context), '.evolver', 'gep');
|
|
75
|
+
const scope = sessionScope();
|
|
76
|
+
return scope ? join(baseDir, 'scopes', scope) : baseDir;
|
|
77
|
+
}
|
|
78
|
+
function pendingSignalBaseDirsForStore(store, context = {}) {
|
|
79
|
+
const repoScoped = Boolean(context.repoRoot);
|
|
80
|
+
const baseDirs = repoScoped
|
|
81
|
+
// Autoexec tasks are repo-scoped; global pending files would be drained by whichever task runs first.
|
|
82
|
+
? [{ baseDir: legacyV1BaseDir(context, false), createIfMissing: false }]
|
|
83
|
+
: [
|
|
84
|
+
{ baseDir: store.baseDir, createIfMissing: true },
|
|
85
|
+
{ baseDir: legacyV1BaseDir(context), createIfMissing: false },
|
|
86
|
+
];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const candidate of baseDirs) {
|
|
90
|
+
const key = resolve(candidate.baseDir);
|
|
91
|
+
if (seen.has(key))
|
|
92
|
+
continue;
|
|
93
|
+
seen.add(key);
|
|
94
|
+
out.push(candidate);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function parsePendingSignals(raw) {
|
|
99
|
+
const parsed = JSON.parse(raw);
|
|
100
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : {};
|
|
101
|
+
}
|
|
102
|
+
function normalizeSignals(rawSignals) {
|
|
103
|
+
const raw = Array.isArray(rawSignals) ? rawSignals : [];
|
|
104
|
+
const signals = raw
|
|
105
|
+
.map((signal) => (typeof signal === 'string' ? signal.trim() : ''))
|
|
106
|
+
.filter((signal) => signal.length > 0 && signal.length <= MAX_SIGNAL_LENGTH);
|
|
107
|
+
return { rawCount: raw.length, signals };
|
|
108
|
+
}
|
|
109
|
+
function writeConsumedFile(path) {
|
|
110
|
+
const tmp = `${path}.tmp`;
|
|
111
|
+
writeFileSync(tmp, `${JSON.stringify({ signals: [], note: '' })}\n`);
|
|
112
|
+
renameSync(tmp, path);
|
|
113
|
+
}
|
|
114
|
+
function consumePendingSignalsFromBaseDir(baseDir, createIfMissing) {
|
|
115
|
+
if (createIfMissing)
|
|
116
|
+
mkdirSync(baseDir, { recursive: true });
|
|
117
|
+
const path = pendingSignalsPath(baseDir);
|
|
118
|
+
if (!existsSync(path))
|
|
119
|
+
return { signals: [] };
|
|
120
|
+
const lockPath = `${path}.lock`;
|
|
121
|
+
acquireLock(lockPath);
|
|
122
|
+
try {
|
|
123
|
+
if (!existsSync(path))
|
|
124
|
+
return { signals: [], path };
|
|
125
|
+
const parsed = parsePendingSignals(readFileSync(path, 'utf8'));
|
|
126
|
+
const { rawCount, signals } = normalizeSignals(parsed.signals);
|
|
127
|
+
if (rawCount > 0)
|
|
128
|
+
writeConsumedFile(path);
|
|
129
|
+
return { signals, path };
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
releaseLock(lockPath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export function consumePendingSignals(baseDir) {
|
|
136
|
+
return consumePendingSignalsFromBaseDir(baseDir, true);
|
|
137
|
+
}
|
|
138
|
+
function tryConsumePendingSignals(baseDir) {
|
|
139
|
+
try {
|
|
140
|
+
return consumePendingSignalsFromBaseDir(baseDir.baseDir, baseDir.createIfMissing);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
console.warn(`[PendingSignals] Failed to consume ${pendingSignalsPath(baseDir.baseDir)}: ${message}`);
|
|
145
|
+
return { signals: [] };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function consumePendingSignalsForStore(store, context = {}) {
|
|
149
|
+
if (!(store instanceof LocalJsonlProvider))
|
|
150
|
+
return { signals: [] };
|
|
151
|
+
const pending = { signals: [] };
|
|
152
|
+
for (const baseDir of pendingSignalBaseDirsForStore(store, context)) {
|
|
153
|
+
const result = tryConsumePendingSignals(baseDir);
|
|
154
|
+
pending.signals.push(...result.signals);
|
|
155
|
+
if (!pending.path && result.path)
|
|
156
|
+
pending.path = result.path;
|
|
157
|
+
}
|
|
158
|
+
return pending;
|
|
159
|
+
}
|
|
160
|
+
export function mergePendingSignalsForStore(store, baseSignals, context = {}) {
|
|
161
|
+
const merged = [...baseSignals];
|
|
162
|
+
const pending = consumePendingSignalsForStore(store, context);
|
|
163
|
+
let injected = 0;
|
|
164
|
+
for (const signal of pending.signals) {
|
|
165
|
+
if (!merged.includes(signal)) {
|
|
166
|
+
merged.push(signal);
|
|
167
|
+
injected++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { signals: merged, injected, ...(pending.path ? { path: pending.path } : {}) };
|
|
171
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type AssetStoreProvider, type AssetRecord, type PutResult } from './provider.js';
|
|
2
|
+
export type ProvenanceSource = 'local' | 'migrated' | 'hub';
|
|
3
|
+
export interface ProvenanceRecord {
|
|
4
|
+
assetId: string;
|
|
5
|
+
source: ProvenanceSource;
|
|
6
|
+
trusted: boolean;
|
|
7
|
+
at: string;
|
|
8
|
+
promotedBy?: string;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Append-only JSONL sidecar (last-write-wins) at <baseDir>/provenance.jsonl. Default for an asset with NO
|
|
13
|
+
* record = trusted: the only local writers (cycleEngine self-produce, v1 migration) are trusted and never
|
|
14
|
+
* write here; the sole untrusted source — hub ingestion — ALWAYS marks via {@link ingestUntrusted}/mark.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ProvenanceStore {
|
|
17
|
+
private readonly now;
|
|
18
|
+
private readonly path;
|
|
19
|
+
private readonly index;
|
|
20
|
+
private loaded;
|
|
21
|
+
constructor(baseDir: string, now?: () => number);
|
|
22
|
+
private load;
|
|
23
|
+
/** Record provenance for an asset_id (append-only; the JSONL history is the audit trail). */
|
|
24
|
+
mark(rec: Omit<ProvenanceRecord, 'at'> & {
|
|
25
|
+
at?: string;
|
|
26
|
+
}): ProvenanceRecord;
|
|
27
|
+
rollbackLast(rec: ProvenanceRecord): void;
|
|
28
|
+
get(assetId: string): ProvenanceRecord | null;
|
|
29
|
+
/** No record → trusted (local default); a record → its trusted flag. */
|
|
30
|
+
isTrusted(assetId: string): boolean;
|
|
31
|
+
/** Explicit, audited untrusted→trusted promotion. Appends a new trusted record carrying who/why. */
|
|
32
|
+
promote(assetId: string, by: string, reason: string): ProvenanceRecord;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The sanctioned hub→local-pool landing: store the asset (store.put recomputes/normalizes the asset_id, so a
|
|
36
|
+
* remote-supplied asset_id is never trusted) and mark it untrusted in the sidecar. This is the ONLY path that
|
|
37
|
+
* should bring hub-fetched assets into the local pool — trust-first from the first byte (#30.1).
|
|
38
|
+
*/
|
|
39
|
+
export declare function ingestUntrusted(store: AssetStoreProvider, prov: ProvenanceStore, record: AssetRecord, source?: ProvenanceSource): Promise<PutResult>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Provenance trust ledger (#30): genes/capsules fetched from the hub are untrusted — a poisoned asset that
|
|
2
|
+
// reaches the selection pool could be reused and spread (experience poisoning). Provenance is a SIDECAR keyed
|
|
3
|
+
// by asset_id, NOT a field on the asset: asset_id = sha256(canonicalize(asset)), and "how we got it" metadata
|
|
4
|
+
// must not enter the content hash (#30.2), or it would break content-addressing. Trust-first by construction:
|
|
5
|
+
// selection defaults to trusted-only; an untrusted asset is promoted to trusted only by an explicit, logged act.
|
|
6
|
+
import { appendFileSync, closeSync, existsSync, openSync, readFileSync, readSync, mkdirSync, statSync, truncateSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { normalizeForPut } from './provider.js';
|
|
9
|
+
/**
|
|
10
|
+
* Append-only JSONL sidecar (last-write-wins) at <baseDir>/provenance.jsonl. Default for an asset with NO
|
|
11
|
+
* record = trusted: the only local writers (cycleEngine self-produce, v1 migration) are trusted and never
|
|
12
|
+
* write here; the sole untrusted source — hub ingestion — ALWAYS marks via {@link ingestUntrusted}/mark.
|
|
13
|
+
*/
|
|
14
|
+
export class ProvenanceStore {
|
|
15
|
+
now;
|
|
16
|
+
path;
|
|
17
|
+
index = new Map();
|
|
18
|
+
loaded = false;
|
|
19
|
+
constructor(baseDir, now = Date.now) {
|
|
20
|
+
this.now = now;
|
|
21
|
+
this.path = join(baseDir, 'provenance.jsonl');
|
|
22
|
+
}
|
|
23
|
+
load() {
|
|
24
|
+
if (this.loaded)
|
|
25
|
+
return;
|
|
26
|
+
if (existsSync(this.path)) {
|
|
27
|
+
for (const line of readFileSync(this.path, 'utf8').split('\n')) {
|
|
28
|
+
if (!line.trim())
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
const r = JSON.parse(line);
|
|
32
|
+
if (r.assetId)
|
|
33
|
+
this.index.set(r.assetId, r);
|
|
34
|
+
}
|
|
35
|
+
catch { /* skip corrupt line */ }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
this.loaded = true;
|
|
39
|
+
}
|
|
40
|
+
/** Record provenance for an asset_id (append-only; the JSONL history is the audit trail). */
|
|
41
|
+
mark(rec) {
|
|
42
|
+
this.load();
|
|
43
|
+
const full = { ...rec, at: rec.at ?? new Date(this.now()).toISOString() };
|
|
44
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
45
|
+
appendFileSync(this.path, `${JSON.stringify(full)}\n`);
|
|
46
|
+
this.index.set(full.assetId, full);
|
|
47
|
+
return full;
|
|
48
|
+
}
|
|
49
|
+
rollbackLast(rec) {
|
|
50
|
+
const line = `${JSON.stringify(rec)}\n`;
|
|
51
|
+
const lineBytes = Buffer.byteLength(line, 'utf8');
|
|
52
|
+
try {
|
|
53
|
+
if (!existsSync(this.path))
|
|
54
|
+
return;
|
|
55
|
+
const stat = statSync(this.path);
|
|
56
|
+
if (!stat.isFile() || stat.size < lineBytes)
|
|
57
|
+
return;
|
|
58
|
+
const offset = stat.size - lineBytes;
|
|
59
|
+
const buf = Buffer.alloc(lineBytes);
|
|
60
|
+
const fd = openSync(this.path, 'r');
|
|
61
|
+
try {
|
|
62
|
+
readSync(fd, buf, 0, lineBytes, offset);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
closeSync(fd);
|
|
66
|
+
}
|
|
67
|
+
if (buf.toString('utf8') !== line)
|
|
68
|
+
return;
|
|
69
|
+
truncateSync(this.path, offset);
|
|
70
|
+
this.index.clear();
|
|
71
|
+
this.loaded = false;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
this.index.clear();
|
|
75
|
+
this.loaded = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
get(assetId) {
|
|
79
|
+
this.load();
|
|
80
|
+
return this.index.get(assetId) ?? null;
|
|
81
|
+
}
|
|
82
|
+
/** No record → trusted (local default); a record → its trusted flag. */
|
|
83
|
+
isTrusted(assetId) {
|
|
84
|
+
this.load();
|
|
85
|
+
const r = this.index.get(assetId);
|
|
86
|
+
return r ? r.trusted : true;
|
|
87
|
+
}
|
|
88
|
+
/** Explicit, audited untrusted→trusted promotion. Appends a new trusted record carrying who/why. */
|
|
89
|
+
promote(assetId, by, reason) {
|
|
90
|
+
const cur = this.get(assetId);
|
|
91
|
+
return this.mark({ assetId, source: cur?.source ?? 'hub', trusted: true, promotedBy: by, reason });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The sanctioned hub→local-pool landing: store the asset (store.put recomputes/normalizes the asset_id, so a
|
|
96
|
+
* remote-supplied asset_id is never trusted) and mark it untrusted in the sidecar. This is the ONLY path that
|
|
97
|
+
* should bring hub-fetched assets into the local pool — trust-first from the first byte (#30.1).
|
|
98
|
+
*/
|
|
99
|
+
export async function ingestUntrusted(store, prov, record, source = 'hub') {
|
|
100
|
+
const normalized = normalizeForPut(record);
|
|
101
|
+
const mark = prov.mark({ assetId: normalized.record.asset_id, source, trusted: false });
|
|
102
|
+
try {
|
|
103
|
+
return await store.put(record);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
prov.rollbackLast(mark);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type AssetKind = 'Gene' | 'Capsule' | 'EvolutionEvent' | 'AntiGene';
|
|
2
|
+
/** 任意带 type/asset_id 的 wire 资产(schema SSOT=gep-sdk, 此处不重定义字段). */
|
|
3
|
+
export interface AssetRecord {
|
|
4
|
+
type: AssetKind;
|
|
5
|
+
asset_id: string;
|
|
6
|
+
[k: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface PutResult {
|
|
9
|
+
asset_id: string;
|
|
10
|
+
stored: boolean;
|
|
11
|
+
verified: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface SearchQuery {
|
|
14
|
+
kind?: AssetKind;
|
|
15
|
+
signalsAny?: string[];
|
|
16
|
+
category?: string;
|
|
17
|
+
gene?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 可插拔资产库接口(D19/M3-1). 一份接口, 多实现: LocalJsonlProvider(本地) + Remote(hub).
|
|
23
|
+
* core 可同时连多个 provider(本地 + 远程), 不造第四套契约.
|
|
24
|
+
*/
|
|
25
|
+
export interface AssetStoreProvider {
|
|
26
|
+
put(asset: AssetRecord): Promise<PutResult>;
|
|
27
|
+
get(assetId: string): Promise<AssetRecord | null>;
|
|
28
|
+
search(query: SearchQuery): Promise<AssetRecord[]>;
|
|
29
|
+
list(kind?: AssetKind, limit?: number): Promise<AssetRecord[]>;
|
|
30
|
+
}
|
|
31
|
+
export declare class AssetIdMismatchError extends Error {
|
|
32
|
+
readonly claimed: string;
|
|
33
|
+
readonly actual: string;
|
|
34
|
+
constructor(claimed: string, actual: string);
|
|
35
|
+
}
|
|
36
|
+
export declare class CapsuleGeneBindingError extends Error {
|
|
37
|
+
constructor();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 落库前规范化(共享给各 provider): 计算/校验 asset_id + 强绑定校验.
|
|
41
|
+
* - 缺 asset_id → 计算填入(verified=false 表示非入参自带).
|
|
42
|
+
* - 带 asset_id → 必须自洽, 否则抛 AssetIdMismatchError.
|
|
43
|
+
* - Capsule.gene 必须非空(M3-4 强绑定).
|
|
44
|
+
*/
|
|
45
|
+
export declare function normalizeForPut(asset: AssetRecord): {
|
|
46
|
+
record: AssetRecord;
|
|
47
|
+
verified: boolean;
|
|
48
|
+
};
|