@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.22
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/assets/gep/genes.jsonl +5 -5
- package/dist/algo/bans.d.ts +10 -1
- package/dist/algo/bans.js +56 -6
- package/dist/algo/candidateAssembly.d.ts +25 -2
- package/dist/algo/candidateAssembly.js +171 -26
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/conversationSniffer.js +25 -8
- package/dist/algo/cycleEngine.d.ts +63 -5
- package/dist/algo/cycleEngine.js +320 -42
- package/dist/algo/cycleFailureClassifier.d.ts +1 -1
- package/dist/algo/evolutionEvent.d.ts +26 -0
- package/dist/algo/evolutionEvent.js +32 -0
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +36 -3
- package/dist/algo/geneHealth.js +47 -4
- package/dist/algo/geneIntake.d.ts +46 -1
- package/dist/algo/geneIntake.js +121 -10
- package/dist/algo/geneSelection.d.ts +126 -6
- package/dist/algo/geneSelection.js +479 -36
- package/dist/algo/index.d.ts +6 -1
- package/dist/algo/index.js +6 -1
- package/dist/algo/kautoProjection.d.ts +41 -0
- package/dist/algo/kautoProjection.js +95 -0
- package/dist/algo/kautoValidator.d.ts +68 -0
- package/dist/algo/kautoValidator.js +256 -0
- package/dist/algo/memoryGraph.d.ts +62 -0
- package/dist/algo/memoryGraph.js +86 -0
- package/dist/algo/orchestrator.d.ts +17 -1
- package/dist/algo/orchestrator.js +30 -4
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/algo/solidify.d.ts +11 -2
- package/dist/algo/solidify.js +37 -7
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetrepair/hubRejection.d.ts +12 -0
- package/dist/assetrepair/hubRejection.js +109 -0
- package/dist/assetrepair/index.d.ts +2 -0
- package/dist/assetrepair/index.js +2 -0
- package/dist/assetrepair/repair.d.ts +33 -0
- package/dist/assetrepair/repair.js +155 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
- package/dist/assetstore/assetSidecarRecords.js +384 -0
- package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
- package/dist/assetstore/assetSidecarRecovery.js +288 -0
- package/dist/assetstore/assetStoreHealth.d.ts +81 -0
- package/dist/assetstore/assetStoreHealth.js +319 -0
- package/dist/assetstore/assetStoreLayout.d.ts +2 -0
- package/dist/assetstore/assetStoreLayout.js +6 -0
- package/dist/assetstore/assetStoreStorage.d.ts +42 -0
- package/dist/assetstore/assetStoreStorage.js +336 -0
- package/dist/assetstore/assetSyncLedger.d.ts +91 -1
- package/dist/assetstore/assetSyncLedger.js +718 -59
- package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
- package/dist/assetstore/foreignJsonlSource.js +150 -0
- package/dist/assetstore/index.d.ts +5 -0
- package/dist/assetstore/index.js +5 -0
- package/dist/assetstore/learningHistory.js +3 -3
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +12 -1
- package/dist/assetstore/localJsonl.js +251 -42
- package/dist/assetstore/provenance.d.ts +93 -4
- package/dist/assetstore/provenance.js +368 -84
- package/dist/assetstore/provider.d.ts +63 -0
- package/dist/assetstore/provider.js +97 -6
- package/dist/assetstore/reviewFilter.d.ts +19 -1
- package/dist/assetstore/reviewFilter.js +39 -1
- package/dist/assetstore/reviewLedger.d.ts +8 -2
- package/dist/assetstore/reviewLedger.js +71 -45
- package/dist/assetstore/unionReadStore.d.ts +25 -0
- package/dist/assetstore/unionReadStore.js +119 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +4 -3
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +4 -3
- package/dist/benchmark/index.d.ts +3 -1
- package/dist/benchmark/index.js +3 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/benchmark/triggerShift.d.ts +62 -0
- package/dist/benchmark/triggerShift.js +106 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +3 -1
- package/dist/bootstrap/index.js +3 -1
- package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
- package/dist/bootstrap/lifecycleBootstrap.js +433 -0
- package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
- package/dist/bootstrap/v1EnvCompat.js +303 -0
- package/dist/events/eventArchive.d.ts +2 -0
- package/dist/events/eventArchive.js +13 -3
- package/dist/events/eventSchema.d.ts +7 -7
- package/dist/events/eventStore.d.ts +2 -0
- package/dist/events/eventStore.js +5 -1
- package/dist/events/ingest.d.ts +2 -1
- package/dist/events/ingest.js +15 -0
- package/dist/events/paths.d.ts +12 -10
- package/dist/events/paths.js +24 -20
- package/dist/events/public.d.ts +2 -2
- package/dist/events/public.js +2 -2
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +68 -4
- package/dist/exec/autoExec.js +392 -33
- package/dist/exec/autonomousCycle.d.ts +31 -4
- package/dist/exec/autonomousCycle.js +71 -13
- package/dist/exec/claudeBridge.d.ts +80 -15
- package/dist/exec/claudeBridge.js +845 -76
- package/dist/exec/executionBinding.d.ts +414 -0
- package/dist/exec/executionBinding.js +588 -0
- package/dist/exec/index.d.ts +1 -0
- package/dist/exec/index.js +1 -0
- package/dist/exec/openPrRegistry.d.ts +8 -2
- package/dist/exec/openPrRegistry.js +32 -22
- package/dist/exec/prompt.js +14 -1
- package/dist/exec/proofOfWork.d.ts +1 -1
- package/dist/exec/proofOfWork.js +2 -2
- package/dist/exec/runnerRegistry.d.ts +153 -36
- package/dist/exec/runnerRegistry.js +848 -65
- package/dist/exec/selfPr.js +1 -7
- package/dist/feedback/envelope.d.ts +61 -0
- package/dist/feedback/envelope.js +168 -0
- package/dist/feedback/index.d.ts +1 -0
- package/dist/feedback/index.js +1 -0
- package/dist/hooks/hooks.js +1 -0
- package/dist/hub/assetCallLog.d.ts +35 -1
- package/dist/hub/assetCallLog.js +124 -1
- package/dist/hub/bindings.d.ts +8 -1
- package/dist/hub/bindings.js +29 -8
- package/dist/hub/capability.d.ts +130 -4
- package/dist/hub/conversationDistiller.d.ts +19 -0
- package/dist/hub/conversationDistiller.js +115 -37
- package/dist/hub/fake.d.ts +3 -2
- package/dist/hub/fake.js +2 -1
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/hub/recipeCompose.d.ts +27 -0
- package/dist/hub/recipeCompose.js +90 -0
- package/dist/hub/sanitize.js +122 -7
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -1
- package/dist/issueReporter/index.d.ts +156 -0
- package/dist/issueReporter/index.js +1688 -0
- package/dist/mailbox/dispatch.d.ts +1 -1
- package/dist/mailbox/dispatch.js +22 -6
- package/dist/mailbox/envelope.d.ts +7 -1
- package/dist/mailbox/envelope.js +9 -2
- package/dist/mailbox/ipcServer.d.ts +12 -2
- package/dist/mailbox/ipcServer.js +183 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- package/dist/modelCompatibility.d.ts +164 -0
- package/dist/modelCompatibility.js +309 -0
- package/dist/observers/valueDigestObserver.d.ts +9 -0
- package/dist/observers/valueDigestObserver.js +35 -2
- package/dist/ops/cleanup.js +1 -1
- package/dist/ops/evolutionGraphProjection.d.ts +20 -0
- package/dist/ops/evolutionGraphProjection.js +315 -0
- package/dist/ops/index.d.ts +2 -1
- package/dist/ops/index.js +2 -1
- package/dist/ops/savingsCore.js +1 -2
- package/dist/ops/selfUpdate.d.ts +18 -1
- package/dist/ops/selfUpdate.js +88 -23
- package/dist/ops/valueOutreach.d.ts +3 -1
- package/dist/ops/valueOutreach.js +5 -1
- package/dist/personality/schema.d.ts +24 -24
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +187 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/proofOfWork.d.ts +125 -6
- package/dist/schema/proofOfWork.js +102 -4
- package/dist/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- package/dist/shadow/shadowHub.js +1 -0
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/cycleHistoryFromEvents.js +17 -8
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +169 -1
- package/dist/signals/extractor.d.ts +2 -2
- package/dist/signals/extractor.js +31 -6
- package/dist/signals/index.d.ts +4 -1
- package/dist/signals/index.js +4 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -0
- package/dist/signals/scopeVocabulary.d.ts +75 -0
- package/dist/signals/scopeVocabulary.js +91 -0
- package/dist/signals/signalGate.js +1 -1
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/strategy/constraintAblation.d.ts +64 -0
- package/dist/strategy/constraintAblation.js +2820 -0
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/strategy/index.d.ts +2 -1
- package/dist/strategy/index.js +2 -1
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +216 -0
- package/dist/trace/learningTrace.js +298 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/trace/trajectory.d.ts +8 -0
- package/dist/trace/trajectory.js +14 -2
- package/dist/util/fetchPort.d.ts +1 -0
- package/dist/util/fetchPort.js +11 -0
- package/dist/util/fileLock.d.ts +113 -7
- package/dist/util/fileLock.js +1035 -80
- package/dist/util/index.d.ts +3 -1
- package/dist/util/index.js +2 -1
- package/dist/verify/index.d.ts +2 -1
- package/dist/verify/index.js +1 -1
- package/dist/verify/sandboxRunner.d.ts +30 -0
- package/dist/verify/sandboxRunner.js +370 -27
- package/dist/verify/sandboxedValidation.d.ts +16 -2
- package/dist/verify/sandboxedValidation.js +200 -22
- package/dist/verify/validation.d.ts +35 -4
- package/dist/verify/validation.js +164 -16
- package/dist/wire/geneHints.d.ts +88 -16
- package/dist/wire/geneHints.js +124 -15
- package/dist/wire/index.d.ts +8 -3
- package/dist/wire/index.js +2 -2
- package/dist/wire/schemaGate.d.ts +21 -0
- package/dist/wire/schemaGate.js +119 -10
- package/dist/workflow/dsl.d.ts +24 -3
- package/dist/workflow/dsl.js +4 -0
- package/dist/workflow/engine.d.ts +5 -1
- package/dist/workflow/engine.js +3 -0
- package/dist/workflow/index.d.ts +3 -1
- package/dist/workflow/index.js +3 -1
- package/dist/workflow/runtime.d.ts +110 -0
- package/dist/workflow/runtime.js +1298 -0
- package/dist/workflow/stateStore.d.ts +172 -0
- package/dist/workflow/stateStore.js +1044 -0
- package/package.json +12 -4
|
@@ -3,10 +3,14 @@ import type { ProvenanceStore } from '../assetstore/provenance.js';
|
|
|
3
3
|
import type { ReviewLedger } from '../assetstore/reviewLedger.js';
|
|
4
4
|
import type { GepCategory } from '../wire/index.js';
|
|
5
5
|
import type { ProblemPattern } from '../schema/problem.js';
|
|
6
|
-
import type { GeneCandidateInput } from './geneSelection.js';
|
|
6
|
+
import type { GeneCandidateInput, SelectionGuardMode } from './geneSelection.js';
|
|
7
7
|
import { CycleEngine, type CycleInput, type CycleResult, type SolidifyPermitGate } from './cycleEngine.js';
|
|
8
8
|
import { type PendingSignalsContext } from '../assetstore/pendingSignals.js';
|
|
9
9
|
import { type ReuseOutcomeSummary, type ReuseOutcomeEvent } from '../ops/reuseOutcomes.js';
|
|
10
|
+
import type { MemoryGraphAdvice } from './memoryGraph.js';
|
|
11
|
+
import type { SelectionPolicy } from './ucb1.js';
|
|
12
|
+
import type { FrozenExecutionBinding } from '../exec/executionBinding.js';
|
|
13
|
+
import type { CompatibilityEvidenceIndex } from '../modelCompatibility.js';
|
|
10
14
|
export interface RunCycleOptions {
|
|
11
15
|
cycleId: string;
|
|
12
16
|
problem: ProblemPattern;
|
|
@@ -19,10 +23,18 @@ export interface RunCycleOptions {
|
|
|
19
23
|
summary: string;
|
|
20
24
|
confidence: number;
|
|
21
25
|
selectionFloor?: number;
|
|
26
|
+
/** Emergency rollback for semantic IDF. Also skips pre-admission corpus collection. */
|
|
27
|
+
disableSemanticIdf?: boolean;
|
|
28
|
+
/** Experimental plateau policy. Omit for current engine-health + random drift behavior. */
|
|
29
|
+
selectionPolicy?: SelectionPolicy;
|
|
30
|
+
/** Versioned relevance-guard rollout. Omit for legacy direct-call behavior. */
|
|
31
|
+
selectionGuard?: SelectionGuardMode;
|
|
22
32
|
/** Optional explicit gene chosen by GEP / an external runtime; forwarded to CycleEngine after assembly. */
|
|
23
33
|
forcedGeneId?: string;
|
|
24
34
|
/** The agent runtime's work: run the mutation, return the outcome. */
|
|
25
35
|
execute: CycleInput['execute'];
|
|
36
|
+
/** Immutable external execution binding; omitted for legacy local cycles. */
|
|
37
|
+
executionBinding?: FrozenExecutionBinding;
|
|
26
38
|
/** Optional triage context forwarded to CycleEngine for cycle.failed classification. */
|
|
27
39
|
failureContext?: CycleInput['failureContext'];
|
|
28
40
|
/** Optional adapter/runtime permit gate. Runs after execute succeeds and before solidify writes assets. */
|
|
@@ -60,6 +72,10 @@ export interface RunCycleOptions {
|
|
|
60
72
|
* agent self-report) gains teeth on selection. Omit → no recall contribution. Never feeds quarantine.
|
|
61
73
|
*/
|
|
62
74
|
recallEvents?: readonly ReuseOutcomeEvent[];
|
|
75
|
+
/** Scoped local MemoryGraph query result. Omit to keep selection unchanged. */
|
|
76
|
+
memoryGraphAdvice?: MemoryGraphAdvice;
|
|
77
|
+
/** Exact model compatibility evidence; quarantine decisions are removed before forced selection. */
|
|
78
|
+
compatibility?: CompatibilityEvidenceIndex;
|
|
63
79
|
}
|
|
64
80
|
/** Drive one full evolution cycle end-to-end: assemble candidates from the store, then run the cycle. */
|
|
65
81
|
export declare function runEvolutionCycle(engine: CycleEngine, store: AssetStoreProvider, opts: RunCycleOptions): Promise<CycleResult>;
|
|
@@ -18,6 +18,12 @@ export async function runEvolutionCycle(engine, store, opts) {
|
|
|
18
18
|
console.warn(`[ExplicitSignals] Failed to consume pending signals (non-fatal): ${message}`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
// Curriculum targets must be present before candidate assembly; injecting them only inside CycleEngine would
|
|
22
|
+
// be too late to recall a gene whose signals_match is curriculum_target:*. The engine repeats this enrichment
|
|
23
|
+
// for direct runCycle callers; the operation is deterministic and set-idempotent.
|
|
24
|
+
const baseSignals = signals;
|
|
25
|
+
const preparedCurriculum = engine.prepareCurriculumSignals(baseSignals);
|
|
26
|
+
const selectionSignals = preparedCurriculum.signals;
|
|
21
27
|
// #268 phase 1 + #274 slice 3: fold cross-runtime reuse counts AND observed recall into the pool (soft re-order).
|
|
22
28
|
// Absent both → no map → default-off. Recall is folded at a lower weight (RECALL_WEIGHT). Assembly sums a gene's
|
|
23
29
|
// ids → sentiment; bounded + clamped in geneSelection; the hard trust/review/ban gates in assembly run first.
|
|
@@ -31,21 +37,39 @@ export async function runEvolutionCycle(engine, store, opts) {
|
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
const asmOpts = {
|
|
40
|
+
includeSemanticCorpus: opts.disableSemanticIdf !== true,
|
|
34
41
|
...(opts.candidateLimit ? { limit: opts.candidateLimit } : {}),
|
|
35
42
|
...(opts.provenance ? { provenance: opts.provenance } : {}),
|
|
36
43
|
...(opts.review ? { review: opts.review } : {}),
|
|
37
44
|
...(opts.includeProbation ? { includeProbation: true } : {}),
|
|
38
45
|
...(opts.hubCandidates && opts.hubCandidates.length > 0 ? { hubCandidates: opts.hubCandidates } : {}),
|
|
39
46
|
...(reuseCounts.size > 0 ? { reuseCounts } : {}),
|
|
47
|
+
...(opts.compatibility ? { compatibility: opts.compatibility } : {}),
|
|
40
48
|
};
|
|
41
|
-
const { candidates, distilledFallback, antiWarnings } = await assembleSelectionPool(store,
|
|
49
|
+
const { candidates: assembledCandidates, distilledFallback, semanticCorpus, antiWarnings } = await assembleSelectionPool(store, selectionSignals, asmOpts);
|
|
50
|
+
const memoryByGene = new Map((opts.memoryGraphAdvice?.genes ?? []).map((evidence) => [evidence.geneId, evidence]));
|
|
51
|
+
const candidates = assembledCandidates.map((candidate) => {
|
|
52
|
+
const evidence = memoryByGene.get(candidate.geneId) ?? (candidate.assetId ? memoryByGene.get(candidate.assetId) : undefined);
|
|
53
|
+
return evidence ? { ...candidate, memoryBoost: evidence.boost } : candidate;
|
|
54
|
+
});
|
|
55
|
+
const memoryDistilledFallback = distilledFallback.map((candidate) => {
|
|
56
|
+
const evidence = memoryByGene.get(candidate.geneId) ?? (candidate.assetId ? memoryByGene.get(candidate.assetId) : undefined);
|
|
57
|
+
return evidence ? { ...candidate, memoryBoost: evidence.boost } : candidate;
|
|
58
|
+
});
|
|
59
|
+
const memoryEligible = [...candidates, ...memoryDistilledFallback];
|
|
60
|
+
const memoryEvidence = opts.memoryGraphAdvice?.genes.filter((evidence) => memoryEligible.some((candidate) => candidate.geneId === evidence.geneId || candidate.assetId === evidence.geneId)).slice(0, 3) ?? [];
|
|
42
61
|
return engine.runCycle({
|
|
43
62
|
cycleId: opts.cycleId,
|
|
44
63
|
problem: opts.problem,
|
|
45
|
-
signals,
|
|
64
|
+
signals: baseSignals,
|
|
65
|
+
curriculumSignals: preparedCurriculum.curriculumSignals,
|
|
46
66
|
category: opts.category,
|
|
47
67
|
...(opts.strategyName !== undefined ? { strategyName: opts.strategyName } : {}),
|
|
48
68
|
candidates,
|
|
69
|
+
semanticCorpus,
|
|
70
|
+
...(opts.disableSemanticIdf ? { disableSemanticIdf: true } : {}),
|
|
71
|
+
...(opts.selectionPolicy ? { selectionPolicy: opts.selectionPolicy } : {}),
|
|
72
|
+
...(opts.selectionGuard ? { selectionGuard: opts.selectionGuard } : {}),
|
|
49
73
|
target: opts.target,
|
|
50
74
|
expectedEffect: opts.expectedEffect,
|
|
51
75
|
summary: opts.summary,
|
|
@@ -53,11 +77,13 @@ export async function runEvolutionCycle(engine, store, opts) {
|
|
|
53
77
|
...(opts.selectionFloor !== undefined ? { selectionFloor: opts.selectionFloor } : {}),
|
|
54
78
|
...(opts.forcedGeneId !== undefined ? { forcedGeneId: opts.forcedGeneId } : {}),
|
|
55
79
|
execute: opts.execute,
|
|
80
|
+
...(opts.executionBinding !== undefined ? { executionBinding: opts.executionBinding } : {}),
|
|
56
81
|
...(opts.failureContext !== undefined ? { failureContext: opts.failureContext } : {}),
|
|
57
82
|
...(opts.solidifyPermit ? { solidifyPermit: opts.solidifyPermit } : {}),
|
|
58
83
|
// #97: forward the distilled-gene fallback pool so a no-signal-match cycle reuses a distilled strategy
|
|
59
|
-
// (instead of a blind innovate) when
|
|
60
|
-
...(
|
|
84
|
+
// (instead of a blind innovate) when normal selection has no reusable positive choice.
|
|
85
|
+
...(memoryDistilledFallback.length > 0 ? { distilledFallback: memoryDistilledFallback } : {}),
|
|
61
86
|
...(antiWarnings.length > 0 ? { antiWarnings } : {}),
|
|
87
|
+
...(memoryEvidence.length > 0 ? { memoryEvidence } : {}),
|
|
62
88
|
});
|
|
63
89
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type GeneLearningView } from '../assetstore/learningHistory.js';
|
|
2
|
+
import type { AssetStoreProvider } from '../assetstore/provider.js';
|
|
3
|
+
export type PublishEligibilityReason = 'eligible' | 'no_proven_success';
|
|
4
|
+
export interface GenePublishEvidence {
|
|
5
|
+
geneId: string;
|
|
6
|
+
/** Real, value-producing successes (inert zero-work successes excluded upstream). */
|
|
7
|
+
success: number;
|
|
8
|
+
failed: number;
|
|
9
|
+
inert: number;
|
|
10
|
+
total: number;
|
|
11
|
+
eligible: boolean;
|
|
12
|
+
reason: PublishEligibilityReason;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The single publish/upload eligibility predicate: a gene may be published iff
|
|
16
|
+
* it has at least one capsule with a `success` outcome.
|
|
17
|
+
*
|
|
18
|
+
* We count `success + inert`, NOT just `success`. aggregateLearningHistory
|
|
19
|
+
* demotes a `success` capsule that carries no `proof_of_work` to `inert`
|
|
20
|
+
* (#195), but the evox capsule builder (asset_builder.rs) does NOT emit
|
|
21
|
+
* `proof_of_work` — a genuine successful run surfaces there as `inert`. Gating
|
|
22
|
+
* publish on the proof-only `success` count would therefore block genes that
|
|
23
|
+
* actually succeeded in production. "Has a success outcome" is the honest bar
|
|
24
|
+
* for the publish gate; the proof/inert distinction stays where it belongs
|
|
25
|
+
* (success-rate, auto-promote), not here. A gene with only failures — or one
|
|
26
|
+
* never run at all — has `success + inert === 0` and is not publishable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isGenePublishEligible(view: Pick<GeneLearningView, 'success' | 'inert'>): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Read-only: derive a gene's outcome evidence and whether it clears the publish
|
|
31
|
+
* bar. Returns `no_proven_success` for a gene the store has never seen succeed,
|
|
32
|
+
* so callers can surface a precise, self-serviceable reason.
|
|
33
|
+
*/
|
|
34
|
+
export declare function assessGenePublishEvidence(store: AssetStoreProvider, geneId: string): Promise<GenePublishEvidence>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Proven-success gate for publishing / uploading a gene (#581).
|
|
2
|
+
//
|
|
3
|
+
// A gene's `confidence` is minted from model self-report (solidify), text
|
|
4
|
+
// completeness (conversationDistiller), or is absent (like) — none of which
|
|
5
|
+
// prove the gene ever WORKED. Publishing such a gene to the market pairs it
|
|
6
|
+
// with a capsule and lets the Hub quality gate reject it after a round-trip
|
|
7
|
+
// ("never truly succeeded"). This predicate is the single, outcome-driven bar
|
|
8
|
+
// every publish/upload surface routes through, so an unproven gene is stopped
|
|
9
|
+
// locally with a clear reason instead of leaking to the Hub.
|
|
10
|
+
//
|
|
11
|
+
// Deliberately DISTINCT from `probationWouldPromote` (genePromotion.ts):
|
|
12
|
+
// auto-promotion runs with NO human in the loop, so it demands a strong, clean
|
|
13
|
+
// record (>= minSuccess successes AND zero failures). Publishing is human-
|
|
14
|
+
// initiated, so the bar is only "has this gene truly succeeded at least once"
|
|
15
|
+
// — a gene with many successes and one stray failure is still worth publishing,
|
|
16
|
+
// and blocking it on `failed === 0` would be wrong.
|
|
17
|
+
import { aggregateLearningHistory } from '../assetstore/learningHistory.js';
|
|
18
|
+
/**
|
|
19
|
+
* The single publish/upload eligibility predicate: a gene may be published iff
|
|
20
|
+
* it has at least one capsule with a `success` outcome.
|
|
21
|
+
*
|
|
22
|
+
* We count `success + inert`, NOT just `success`. aggregateLearningHistory
|
|
23
|
+
* demotes a `success` capsule that carries no `proof_of_work` to `inert`
|
|
24
|
+
* (#195), but the evox capsule builder (asset_builder.rs) does NOT emit
|
|
25
|
+
* `proof_of_work` — a genuine successful run surfaces there as `inert`. Gating
|
|
26
|
+
* publish on the proof-only `success` count would therefore block genes that
|
|
27
|
+
* actually succeeded in production. "Has a success outcome" is the honest bar
|
|
28
|
+
* for the publish gate; the proof/inert distinction stays where it belongs
|
|
29
|
+
* (success-rate, auto-promote), not here. A gene with only failures — or one
|
|
30
|
+
* never run at all — has `success + inert === 0` and is not publishable.
|
|
31
|
+
*/
|
|
32
|
+
export function isGenePublishEligible(view) {
|
|
33
|
+
return view.success + (view.inert ?? 0) >= 1;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read-only: derive a gene's outcome evidence and whether it clears the publish
|
|
37
|
+
* bar. Returns `no_proven_success` for a gene the store has never seen succeed,
|
|
38
|
+
* so callers can surface a precise, self-serviceable reason.
|
|
39
|
+
*/
|
|
40
|
+
export async function assessGenePublishEvidence(store, geneId) {
|
|
41
|
+
const view = await aggregateLearningHistory(store, geneId);
|
|
42
|
+
const eligible = isGenePublishEligible(view);
|
|
43
|
+
return {
|
|
44
|
+
geneId,
|
|
45
|
+
success: view.success,
|
|
46
|
+
failed: view.failed,
|
|
47
|
+
inert: view.inert ?? 0,
|
|
48
|
+
total: view.total,
|
|
49
|
+
eligible,
|
|
50
|
+
reason: eligible ? 'eligible' : 'no_proven_success',
|
|
51
|
+
};
|
|
52
|
+
}
|
package/dist/algo/solidify.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { type Capsule } from '../wire/index.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type ProofOfWork } from '../schema/proofOfWork.js';
|
|
3
3
|
export type ResolutionStatus = 'pending' | 'suppressed_observationally' | 'resolved_by_evidence' | 'regressed' | 'inconclusive';
|
|
4
|
+
export interface FailureEvidenceIdentityInput {
|
|
5
|
+
failureId?: string;
|
|
6
|
+
rootAttemptId?: string;
|
|
7
|
+
executionId?: string;
|
|
8
|
+
verifierDigest?: string;
|
|
9
|
+
artifactDigest?: string;
|
|
10
|
+
}
|
|
4
11
|
export interface SolidifyInput {
|
|
5
12
|
geneId: string;
|
|
6
13
|
trigger: readonly string[];
|
|
@@ -11,6 +18,8 @@ export interface SolidifyInput {
|
|
|
11
18
|
score: number;
|
|
12
19
|
};
|
|
13
20
|
proofOfWork?: ProofOfWork;
|
|
21
|
+
/** Failure-threshold identity. Stored only for failed Capsules; retry roots dedupe automatic fan-out. */
|
|
22
|
+
failureIdentity?: FailureEvidenceIdentityInput;
|
|
14
23
|
/** 是否有强证据(validation/测试在证据下通过). 默认 false → 不自动升级到 resolved. */
|
|
15
24
|
strongEvidence?: boolean;
|
|
16
25
|
/** regressed 判定阈: 失败且分低于此 → regressed, 否则 inconclusive. 默认 0.3. */
|
|
@@ -22,7 +31,7 @@ export interface SolidifyResult {
|
|
|
22
31
|
producedValue: boolean;
|
|
23
32
|
reasons: string[];
|
|
24
33
|
}
|
|
25
|
-
/** ProofOfWork 是否表明有实际产出(解锁非 coding agent, 批注#17/#19). */
|
|
34
|
+
/** ProofOfWork 是否表明有实际产出(解锁非 coding agent, 批注#17/#19). #961: 兼容读 helper(snake_case 优先, 回退旧 camelCase 存量). */
|
|
26
35
|
export declare function proofIndicatesOutput(p?: ProofOfWork): boolean;
|
|
27
36
|
/**
|
|
28
37
|
* 两级 resolution(M4A-8) + ProofOfWork 判价值(M4A-5):
|
package/dist/algo/solidify.js
CHANGED
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
import { ulid as makeUlid } from 'ulid';
|
|
2
2
|
import { computeAssetId, SCHEMA_VERSION } from '../wire/index.js';
|
|
3
|
-
|
|
3
|
+
import { gitDiffOf, artifactHashOf, externalReceiptOf, toolCallTraceOf, proofOfWorkForWrite } from '../schema/proofOfWork.js';
|
|
4
|
+
/** ProofOfWork 是否表明有实际产出(解锁非 coding agent, 批注#17/#19). #961: 兼容读 helper(snake_case 优先, 回退旧 camelCase 存量). */
|
|
4
5
|
export function proofIndicatesOutput(p) {
|
|
5
6
|
if (!p)
|
|
6
7
|
return false;
|
|
7
8
|
switch (p.kind) {
|
|
8
|
-
case 'git_diff': return (p
|
|
9
|
-
case 'artifact_hash': return Boolean(p
|
|
10
|
-
case 'external_receipt':
|
|
11
|
-
|
|
9
|
+
case 'git_diff': return (gitDiffOf(p)?.files ?? 0) > 0 || (gitDiffOf(p)?.lines ?? 0) > 0;
|
|
10
|
+
case 'artifact_hash': return Boolean(artifactHashOf(p)?.sha256);
|
|
11
|
+
case 'external_receipt': {
|
|
12
|
+
const r = externalReceiptOf(p);
|
|
13
|
+
return Boolean(r?.receipt_id ?? r?.receiptId);
|
|
14
|
+
}
|
|
15
|
+
case 'tool_call_trace': return (toolCallTraceOf(p)?.calls ?? 0) > 0;
|
|
12
16
|
default: return false;
|
|
13
17
|
}
|
|
14
18
|
}
|
|
19
|
+
function nonEmpty(s) {
|
|
20
|
+
return typeof s === 'string' && s.trim().length > 0 ? s.trim() : undefined;
|
|
21
|
+
}
|
|
22
|
+
function failureIdentityTrace(input) {
|
|
23
|
+
if (input.outcome.status !== 'failed' || !input.failureIdentity)
|
|
24
|
+
return undefined;
|
|
25
|
+
const trace = { stage: 'validate' };
|
|
26
|
+
const root = nonEmpty(input.failureIdentity.rootAttemptId);
|
|
27
|
+
const execution = nonEmpty(input.failureIdentity.executionId);
|
|
28
|
+
const failure = nonEmpty(input.failureIdentity.failureId);
|
|
29
|
+
const verifier = nonEmpty(input.failureIdentity.verifierDigest);
|
|
30
|
+
const artifact = nonEmpty(input.failureIdentity.artifactDigest);
|
|
31
|
+
if (root)
|
|
32
|
+
trace['root_attempt_id'] = root;
|
|
33
|
+
if (execution)
|
|
34
|
+
trace['execution_id'] = execution;
|
|
35
|
+
if (failure)
|
|
36
|
+
trace['failure_id'] = failure;
|
|
37
|
+
if (verifier)
|
|
38
|
+
trace['verifier_digest'] = verifier;
|
|
39
|
+
if (artifact)
|
|
40
|
+
trace['artifact_digest'] = artifact;
|
|
41
|
+
return Object.keys(trace).length > 1 ? [trace] : undefined;
|
|
42
|
+
}
|
|
15
43
|
/**
|
|
16
44
|
* 两级 resolution(M4A-8) + ProofOfWork 判价值(M4A-5):
|
|
17
45
|
* - 失败: 分 < 阈 → regressed, 否则 inconclusive.
|
|
@@ -44,7 +72,8 @@ export function solidify(input) {
|
|
|
44
72
|
// (artifact_hash/external_receipt/tool_call_trace) gd is undefined and blast falls back to {0,0} — which here
|
|
45
73
|
// means "blast unknown", NOT "no change". Consumers that read blast=0 as a no-op signal (e.g. cycleFailure's
|
|
46
74
|
// local_gene_no_blast bucket) MUST first confirm the proof kind is git_diff; the cycleEngine guard does this.
|
|
47
|
-
const gd = input.proofOfWork?.kind === 'git_diff' ? input.proofOfWork
|
|
75
|
+
const gd = input.proofOfWork?.kind === 'git_diff' ? gitDiffOf(input.proofOfWork) : undefined;
|
|
76
|
+
const identityTrace = failureIdentityTrace(input);
|
|
48
77
|
const base = {
|
|
49
78
|
type: 'Capsule',
|
|
50
79
|
schema_version: SCHEMA_VERSION,
|
|
@@ -56,7 +85,8 @@ export function solidify(input) {
|
|
|
56
85
|
blast_radius: { files: gd?.files ?? 0, lines: gd?.lines ?? 0 },
|
|
57
86
|
outcome: input.outcome,
|
|
58
87
|
resolution_status: status,
|
|
59
|
-
...(input.proofOfWork ? { proof_of_work: input.proofOfWork } : {}),
|
|
88
|
+
...(input.proofOfWork ? { proof_of_work: proofOfWorkForWrite(input.proofOfWork) } : {}),
|
|
89
|
+
...(identityTrace ? { execution_trace: identityTrace } : {}),
|
|
60
90
|
};
|
|
61
91
|
const asset_id = computeAssetId(base);
|
|
62
92
|
return { capsule: { ...base, asset_id }, resolutionStatus: status, producedValue, reasons };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { RootEvent } from '../events/eventSchema.js';
|
|
2
|
+
export declare const SELECTION_POLICIES: readonly ["engine-health", "ucb1-shadow", "ucb1"];
|
|
3
|
+
export type SelectionPolicy = (typeof SELECTION_POLICIES)[number];
|
|
4
|
+
export declare const UCB1_SELECTION_POLICY_VERSION = "ucb1-v1";
|
|
5
|
+
export declare const UCB1_REWARD_POLICY_VERSION = "productive-binary-v1";
|
|
6
|
+
export interface Ucb1ArmStats {
|
|
7
|
+
armId: string;
|
|
8
|
+
pulls: number;
|
|
9
|
+
completedPulls: number;
|
|
10
|
+
rewardSum: number;
|
|
11
|
+
meanReward: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Ucb1History {
|
|
14
|
+
arms: ReadonlyMap<string, Ucb1ArmStats>;
|
|
15
|
+
/** Every non-ad-hoc decision counts immediately, including decisions whose terminal event is still pending. */
|
|
16
|
+
totalPulls: number;
|
|
17
|
+
}
|
|
18
|
+
export interface Ucb1Arm {
|
|
19
|
+
armId: string;
|
|
20
|
+
baseScore: number;
|
|
21
|
+
explorationEligible: boolean;
|
|
22
|
+
stats: Ucb1ArmStats;
|
|
23
|
+
}
|
|
24
|
+
export interface Ucb1Choice {
|
|
25
|
+
armId: string;
|
|
26
|
+
pulls: number;
|
|
27
|
+
completedPulls: number;
|
|
28
|
+
totalPulls: number;
|
|
29
|
+
meanReward: number;
|
|
30
|
+
/** Null represents the canonical +Infinity cold-start index without putting Infinity on the event wire. */
|
|
31
|
+
bonus: number | null;
|
|
32
|
+
/** Null represents the canonical +Infinity cold-start index without putting Infinity on the event wire. */
|
|
33
|
+
index: number | null;
|
|
34
|
+
coldStart: boolean;
|
|
35
|
+
}
|
|
36
|
+
export type Ucb1FallbackReason = 'empty_pool' | 'ineligible_arm' | 'missing_history';
|
|
37
|
+
export interface Ucb1Decision {
|
|
38
|
+
choice: Ucb1Choice | null;
|
|
39
|
+
fallbackReason?: Ucb1FallbackReason;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Rebuild UCB1 state from the append-only root event log. Decisions are pulls immediately, so later readers no
|
|
43
|
+
* longer treat an in-flight arm as cold. The read-select-append sequence is not an atomic reservation across
|
|
44
|
+
* workers. A terminal event adds reward exactly once per cycle; duplicate rows collapse to their latest projection.
|
|
45
|
+
*/
|
|
46
|
+
export declare function deriveUcb1History(events: readonly RootEvent[]): Ucb1History;
|
|
47
|
+
/** Combine current asset identity with legacy gene-only history when that bridge is unambiguous. */
|
|
48
|
+
export declare function ucb1StatsForCandidate(history: Ucb1History, geneId: string, assetId?: string, includeLegacyGeneHistory?: boolean): Ucb1ArmStats;
|
|
49
|
+
/**
|
|
50
|
+
* Canonical UCB1: mean reward + sqrt(2 ln(total pulls) / arm pulls). Cold arms have +Infinity and are ordered
|
|
51
|
+
* deterministically by base score then arm identity. The caller supplies only the already-gated exploration window.
|
|
52
|
+
*/
|
|
53
|
+
export declare function chooseUcb1Arm(arms: readonly Ucb1Arm[], historyTotalPulls: number): Ucb1Decision;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export const SELECTION_POLICIES = ['engine-health', 'ucb1-shadow', 'ucb1'];
|
|
2
|
+
export const UCB1_SELECTION_POLICY_VERSION = 'ucb1-v1';
|
|
3
|
+
export const UCB1_REWARD_POLICY_VERSION = 'productive-binary-v1';
|
|
4
|
+
function nonEmptyString(value) {
|
|
5
|
+
if (typeof value !== 'string')
|
|
6
|
+
return undefined;
|
|
7
|
+
const normalized = value.trim();
|
|
8
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
9
|
+
}
|
|
10
|
+
function decisionProjection(event) {
|
|
11
|
+
if (event.type !== 'decision.gene_selected')
|
|
12
|
+
return undefined;
|
|
13
|
+
const payload = event.payload;
|
|
14
|
+
const cycleId = nonEmptyString(payload['cycleId']);
|
|
15
|
+
const geneId = nonEmptyString(payload['selectedGeneId']);
|
|
16
|
+
if (!cycleId)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (!geneId || geneId === 'ad-hoc')
|
|
19
|
+
return { cycleId, decision: null };
|
|
20
|
+
return {
|
|
21
|
+
cycleId,
|
|
22
|
+
decision: { armId: nonEmptyString(payload['selectedAssetId']) ?? geneId },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function terminalReward(event) {
|
|
26
|
+
if (event.type !== 'cycle.failed' && event.type !== 'cycle.solidified')
|
|
27
|
+
return null;
|
|
28
|
+
const payload = event.payload;
|
|
29
|
+
const cycleId = nonEmptyString(payload['cycleId']);
|
|
30
|
+
if (!cycleId)
|
|
31
|
+
return null;
|
|
32
|
+
if (event.type === 'cycle.failed' || payload['producedValue'] === false)
|
|
33
|
+
return { cycleId, reward: 0 };
|
|
34
|
+
// Legacy solidified events predate producedValue. Preserve the existing confidence replay compatibility policy.
|
|
35
|
+
return { cycleId, reward: 1 };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Rebuild UCB1 state from the append-only root event log. Decisions are pulls immediately, so later readers no
|
|
39
|
+
* longer treat an in-flight arm as cold. The read-select-append sequence is not an atomic reservation across
|
|
40
|
+
* workers. A terminal event adds reward exactly once per cycle; duplicate rows collapse to their latest projection.
|
|
41
|
+
*/
|
|
42
|
+
export function deriveUcb1History(events) {
|
|
43
|
+
const cycles = new Map();
|
|
44
|
+
for (const event of events) {
|
|
45
|
+
const decision = decisionProjection(event);
|
|
46
|
+
// A restarted/re-emitted decision for the same cycleId begins a new pending lifecycle. Never let a terminal
|
|
47
|
+
// from the earlier lifecycle leak reward into this newer arm.
|
|
48
|
+
if (decision) {
|
|
49
|
+
if (decision.decision)
|
|
50
|
+
cycles.set(decision.cycleId, { decision: decision.decision });
|
|
51
|
+
else
|
|
52
|
+
cycles.delete(decision.cycleId);
|
|
53
|
+
}
|
|
54
|
+
const terminal = terminalReward(event);
|
|
55
|
+
if (terminal) {
|
|
56
|
+
const cycle = cycles.get(terminal.cycleId);
|
|
57
|
+
if (cycle)
|
|
58
|
+
cycle.reward = terminal.reward;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const mutable = new Map();
|
|
62
|
+
for (const cycle of cycles.values()) {
|
|
63
|
+
const current = mutable.get(cycle.decision.armId) ?? { pulls: 0, completedPulls: 0, rewardSum: 0 };
|
|
64
|
+
current.pulls += 1;
|
|
65
|
+
if (cycle.reward !== undefined) {
|
|
66
|
+
current.completedPulls += 1;
|
|
67
|
+
current.rewardSum += cycle.reward;
|
|
68
|
+
}
|
|
69
|
+
mutable.set(cycle.decision.armId, current);
|
|
70
|
+
}
|
|
71
|
+
const arms = new Map();
|
|
72
|
+
for (const [armId, stats] of mutable) {
|
|
73
|
+
arms.set(armId, {
|
|
74
|
+
armId,
|
|
75
|
+
...stats,
|
|
76
|
+
meanReward: stats.completedPulls > 0 ? stats.rewardSum / stats.completedPulls : 0,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return { arms, totalPulls: cycles.size };
|
|
80
|
+
}
|
|
81
|
+
function emptyStats(armId) {
|
|
82
|
+
return { armId, pulls: 0, completedPulls: 0, rewardSum: 0, meanReward: 0 };
|
|
83
|
+
}
|
|
84
|
+
/** Combine current asset identity with legacy gene-only history when that bridge is unambiguous. */
|
|
85
|
+
export function ucb1StatsForCandidate(history, geneId, assetId, includeLegacyGeneHistory = true) {
|
|
86
|
+
const armId = assetId ?? geneId;
|
|
87
|
+
const identities = assetId === undefined
|
|
88
|
+
? [geneId]
|
|
89
|
+
: [...new Set([assetId, ...(includeLegacyGeneHistory ? [geneId] : [])])];
|
|
90
|
+
const matches = identities.map((identity) => history.arms.get(identity)).filter((stats) => stats !== undefined);
|
|
91
|
+
if (matches.length === 0)
|
|
92
|
+
return emptyStats(armId);
|
|
93
|
+
const pulls = matches.reduce((sum, stats) => sum + stats.pulls, 0);
|
|
94
|
+
const completedPulls = matches.reduce((sum, stats) => sum + stats.completedPulls, 0);
|
|
95
|
+
const rewardSum = matches.reduce((sum, stats) => sum + stats.rewardSum, 0);
|
|
96
|
+
return {
|
|
97
|
+
armId,
|
|
98
|
+
pulls,
|
|
99
|
+
completedPulls,
|
|
100
|
+
rewardSum,
|
|
101
|
+
meanReward: completedPulls > 0 ? rewardSum / completedPulls : 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function finiteBaseScore(value) {
|
|
105
|
+
return Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY;
|
|
106
|
+
}
|
|
107
|
+
function finiteCount(value) {
|
|
108
|
+
return Number.isFinite(value) ? Math.min(Number.MAX_SAFE_INTEGER, Math.floor(Math.max(0, value))) : 0;
|
|
109
|
+
}
|
|
110
|
+
function finiteMeanReward(value) {
|
|
111
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
|
|
112
|
+
}
|
|
113
|
+
function saturatingCountSum(total, value) {
|
|
114
|
+
const count = finiteCount(value);
|
|
115
|
+
return total >= Number.MAX_SAFE_INTEGER - count ? Number.MAX_SAFE_INTEGER : total + count;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Canonical UCB1: mean reward + sqrt(2 ln(total pulls) / arm pulls). Cold arms have +Infinity and are ordered
|
|
119
|
+
* deterministically by base score then arm identity. The caller supplies only the already-gated exploration window.
|
|
120
|
+
*/
|
|
121
|
+
export function chooseUcb1Arm(arms, historyTotalPulls) {
|
|
122
|
+
if (arms.length === 0)
|
|
123
|
+
return { choice: null, fallbackReason: 'empty_pool' };
|
|
124
|
+
if (arms.some((arm) => !arm.explorationEligible))
|
|
125
|
+
return { choice: null, fallbackReason: 'ineligible_arm' };
|
|
126
|
+
const totalPulls = Math.max(1, finiteCount(historyTotalPulls), arms.reduce((sum, arm) => saturatingCountSum(sum, arm.stats.pulls), 0));
|
|
127
|
+
const ranked = arms.map((arm) => {
|
|
128
|
+
const pulls = finiteCount(arm.stats.pulls);
|
|
129
|
+
const meanReward = finiteMeanReward(arm.stats.meanReward);
|
|
130
|
+
const coldStart = pulls === 0;
|
|
131
|
+
const bonus = coldStart ? null : Math.sqrt((2 * Math.log(totalPulls)) / pulls);
|
|
132
|
+
const index = coldStart ? null : meanReward + bonus;
|
|
133
|
+
return { arm, pulls, meanReward, coldStart, bonus, index };
|
|
134
|
+
}).sort((left, right) => {
|
|
135
|
+
if (left.coldStart !== right.coldStart)
|
|
136
|
+
return left.coldStart ? -1 : 1;
|
|
137
|
+
if (left.index !== null && right.index !== null && left.index !== right.index)
|
|
138
|
+
return right.index - left.index;
|
|
139
|
+
const leftScore = finiteBaseScore(left.arm.baseScore);
|
|
140
|
+
const rightScore = finiteBaseScore(right.arm.baseScore);
|
|
141
|
+
return rightScore !== leftScore ? rightScore - leftScore : left.arm.armId.localeCompare(right.arm.armId);
|
|
142
|
+
});
|
|
143
|
+
const winner = ranked[0];
|
|
144
|
+
return {
|
|
145
|
+
choice: {
|
|
146
|
+
armId: winner.arm.armId,
|
|
147
|
+
pulls: winner.pulls,
|
|
148
|
+
completedPulls: finiteCount(winner.arm.stats.completedPulls),
|
|
149
|
+
totalPulls,
|
|
150
|
+
meanReward: winner.meanReward,
|
|
151
|
+
bonus: winner.bonus,
|
|
152
|
+
index: winner.index,
|
|
153
|
+
coldStart: winner.coldStart,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RepairIssue } from './repair.js';
|
|
2
|
+
/** Field-level issues reported by the Hub, keyed to the asset index they belong to (`-1` = bundle-level). */
|
|
3
|
+
export interface HubRejectionReport {
|
|
4
|
+
issues: RepairIssue[];
|
|
5
|
+
/** Issues for one asset of the published bundle, by its position in `payload.assets`. */
|
|
6
|
+
byAssetIndex: Map<number, RepairIssue[]>;
|
|
7
|
+
}
|
|
8
|
+
export interface HubRejectionOptions {
|
|
9
|
+
/** The bundle's asset types in `payload.assets` order, so a `<type>_*` rule reason routes to its own asset. */
|
|
10
|
+
assetTypes?: readonly (string | undefined)[];
|
|
11
|
+
}
|
|
12
|
+
export declare function hubRejectionIssues(body: unknown, options?: HubRejectionOptions): HubRejectionReport;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const MAX_ISSUES = 50;
|
|
2
|
+
const MAX_MESSAGE_CHARS = 300;
|
|
3
|
+
const REASON_TOKEN = /^[a-z][a-z0-9_]*$/;
|
|
4
|
+
const REASON_PREFIXES = [
|
|
5
|
+
{ prefix: 'gene', assetType: 'Gene' },
|
|
6
|
+
{ prefix: 'capsule', assetType: 'Capsule' },
|
|
7
|
+
{ prefix: 'event', assetType: 'EvolutionEvent' },
|
|
8
|
+
{ prefix: 'bundle' },
|
|
9
|
+
];
|
|
10
|
+
const REPAIRABLE_REASON_PATHS = new Map([
|
|
11
|
+
['asset_id_verification_failed', 'asset_id'],
|
|
12
|
+
['missing_asset_id', 'asset_id'],
|
|
13
|
+
]);
|
|
14
|
+
export function hubRejectionIssues(body, options = {}) {
|
|
15
|
+
const fieldReport = fieldLevelIssues(body);
|
|
16
|
+
if (fieldReport.issues.length > 0)
|
|
17
|
+
return fieldReport;
|
|
18
|
+
return ruleReasonIssues(body, options.assetTypes ?? []);
|
|
19
|
+
}
|
|
20
|
+
function fieldLevelIssues(body) {
|
|
21
|
+
const report = { issues: [], byAssetIndex: new Map() };
|
|
22
|
+
for (const raw of detailRows(body).slice(0, MAX_ISSUES)) {
|
|
23
|
+
const detail = asRecord(raw);
|
|
24
|
+
if (!detail)
|
|
25
|
+
continue;
|
|
26
|
+
const path = pathSegments(detail['path']);
|
|
27
|
+
const message = text(detail['message']) ?? text(detail['code']);
|
|
28
|
+
if (!message)
|
|
29
|
+
continue;
|
|
30
|
+
const issue = {
|
|
31
|
+
path: assetRelativePath(path),
|
|
32
|
+
keyword: text(detail['code']) ?? 'invalid',
|
|
33
|
+
message: message.slice(0, MAX_MESSAGE_CHARS),
|
|
34
|
+
source: 'hub',
|
|
35
|
+
};
|
|
36
|
+
report.issues.push(issue);
|
|
37
|
+
const index = assetIndex(path);
|
|
38
|
+
const bucket = report.byAssetIndex.get(index);
|
|
39
|
+
if (bucket)
|
|
40
|
+
bucket.push(issue);
|
|
41
|
+
else
|
|
42
|
+
report.byAssetIndex.set(index, [issue]);
|
|
43
|
+
}
|
|
44
|
+
return report;
|
|
45
|
+
}
|
|
46
|
+
function ruleReasonIssues(body, assetTypes) {
|
|
47
|
+
const report = { issues: [], byAssetIndex: new Map() };
|
|
48
|
+
const reason = ruleReason(body);
|
|
49
|
+
if (!reason)
|
|
50
|
+
return report;
|
|
51
|
+
const prefix = REASON_PREFIXES.find((entry) => reason.token.startsWith(`${entry.prefix}_`));
|
|
52
|
+
if (!prefix)
|
|
53
|
+
return report;
|
|
54
|
+
const issue = {
|
|
55
|
+
path: REPAIRABLE_REASON_PATHS.get(reason.token.slice(prefix.prefix.length + 1)) ?? '',
|
|
56
|
+
keyword: reason.token,
|
|
57
|
+
message: reason.message.slice(0, MAX_MESSAGE_CHARS),
|
|
58
|
+
source: 'hub',
|
|
59
|
+
};
|
|
60
|
+
const index = prefix.assetType === undefined ? -1 : assetTypes.indexOf(prefix.assetType);
|
|
61
|
+
report.issues.push(issue);
|
|
62
|
+
report.byAssetIndex.set(index, [issue]);
|
|
63
|
+
return report;
|
|
64
|
+
}
|
|
65
|
+
function ruleReason(body) {
|
|
66
|
+
const root = asRecord(body);
|
|
67
|
+
if (!root)
|
|
68
|
+
return undefined;
|
|
69
|
+
const payload = asRecord(root['payload']);
|
|
70
|
+
const raw = [root['error'], root['reason'], payload?.['error'], payload?.['reason']]
|
|
71
|
+
.map((candidate) => text(candidate))
|
|
72
|
+
.find((candidate) => candidate !== undefined);
|
|
73
|
+
if (!raw)
|
|
74
|
+
return undefined;
|
|
75
|
+
const token = raw.split(":", 1)[0]?.trim() ?? '';
|
|
76
|
+
return REASON_TOKEN.test(token) ? { token, message: raw } : undefined;
|
|
77
|
+
}
|
|
78
|
+
function detailRows(body) {
|
|
79
|
+
const root = asRecord(body);
|
|
80
|
+
if (!root)
|
|
81
|
+
return [];
|
|
82
|
+
const payload = asRecord(root['payload']);
|
|
83
|
+
for (const candidate of [root['details'], root['errors'], payload?.['details'], payload?.['errors']]) {
|
|
84
|
+
if (Array.isArray(candidate))
|
|
85
|
+
return candidate;
|
|
86
|
+
}
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
function pathSegments(value) {
|
|
90
|
+
if (!Array.isArray(value))
|
|
91
|
+
return typeof value === 'string' ? value.split('.') : [];
|
|
92
|
+
return value.filter((segment) => typeof segment === 'string' || typeof segment === 'number');
|
|
93
|
+
}
|
|
94
|
+
/** `['payload','assets',0,'constraints','forbidden_paths']` → `constraints.forbidden_paths`. */
|
|
95
|
+
function assetRelativePath(path) {
|
|
96
|
+
const at = path.findIndex((segment) => typeof segment === 'number');
|
|
97
|
+
const tail = at >= 0 ? path.slice(at + 1) : path.filter((segment) => segment !== 'payload');
|
|
98
|
+
return tail.join('.');
|
|
99
|
+
}
|
|
100
|
+
function assetIndex(path) {
|
|
101
|
+
const found = path.find((segment) => typeof segment === 'number');
|
|
102
|
+
return typeof found === 'number' ? found : -1;
|
|
103
|
+
}
|
|
104
|
+
function text(value) {
|
|
105
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
106
|
+
}
|
|
107
|
+
function asRecord(value) {
|
|
108
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
109
|
+
}
|