@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
|
@@ -1,4 +1,87 @@
|
|
|
1
|
-
import { computeAssetId, verifyAssetId } from '../wire/index.js';
|
|
1
|
+
import { canonicalize, computeAssetId, verifyAssetId } from '../wire/index.js';
|
|
2
|
+
export class FrozenAssetIdCollisionError extends Error {
|
|
3
|
+
assetId;
|
|
4
|
+
code = 'FROZEN_ASSET_ID_COLLISION';
|
|
5
|
+
constructor(assetId) {
|
|
6
|
+
super('frozen asset_id already exists with different content');
|
|
7
|
+
this.assetId = assetId;
|
|
8
|
+
this.name = 'FrozenAssetIdCollisionError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class InvalidFrozenPutResultError extends Error {
|
|
12
|
+
code = 'INVALID_FROZEN_PUT_RESULT';
|
|
13
|
+
constructor() {
|
|
14
|
+
super('invalid frozen put result');
|
|
15
|
+
this.name = 'InvalidFrozenPutResultError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function frozenAssetRecordsEqual(left, right) {
|
|
19
|
+
return canonicalize(left) === canonicalize(right);
|
|
20
|
+
}
|
|
21
|
+
export function validateFrozenPutResult(value, expectedAssetId) {
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
23
|
+
throw new InvalidFrozenPutResultError();
|
|
24
|
+
const result = value;
|
|
25
|
+
if (result['asset_id'] !== expectedAssetId
|
|
26
|
+
|| typeof result['stored'] !== 'boolean'
|
|
27
|
+
|| result['verified'] !== false) {
|
|
28
|
+
throw new InvalidFrozenPutResultError();
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
export class InvalidConditionalPutResultError extends Error {
|
|
33
|
+
reason;
|
|
34
|
+
code = 'INVALID_CONDITIONAL_PUT_RESULT';
|
|
35
|
+
constructor(reason) {
|
|
36
|
+
super(`invalid conditional put result: ${reason}`);
|
|
37
|
+
this.reason = reason;
|
|
38
|
+
this.name = 'InvalidConditionalPutResultError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Validate an injected provider response before callers treat it as an explicit write/no-write decision. */
|
|
42
|
+
export function validateConditionalPutResult(value, expectedAssetId, options) {
|
|
43
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
44
|
+
throw new InvalidConditionalPutResultError('malformed_result');
|
|
45
|
+
}
|
|
46
|
+
const result = value;
|
|
47
|
+
const status = result['status'];
|
|
48
|
+
const collisionWithAssetId = result['collisionWithAssetId'];
|
|
49
|
+
if (typeof result['asset_id'] !== 'string'
|
|
50
|
+
|| typeof result['stored'] !== 'boolean'
|
|
51
|
+
|| typeof result['verified'] !== 'boolean'
|
|
52
|
+
|| (status !== 'stored' && status !== 'already_exists' && status !== 'logical_collision')
|
|
53
|
+
|| (result['logicalId'] !== undefined
|
|
54
|
+
&& (typeof result['logicalId'] !== 'string'
|
|
55
|
+
|| !result['logicalId'].trim()
|
|
56
|
+
|| result['logicalId'] !== result['logicalId'].trim()))
|
|
57
|
+
|| (collisionWithAssetId !== undefined
|
|
58
|
+
&& (typeof collisionWithAssetId !== 'string'
|
|
59
|
+
|| !collisionWithAssetId.trim()
|
|
60
|
+
|| collisionWithAssetId !== collisionWithAssetId.trim()))) {
|
|
61
|
+
throw new InvalidConditionalPutResultError('malformed_result');
|
|
62
|
+
}
|
|
63
|
+
if (result['asset_id'] !== expectedAssetId) {
|
|
64
|
+
throw new InvalidConditionalPutResultError('asset_id_mismatch');
|
|
65
|
+
}
|
|
66
|
+
if (result['stored'] !== (status === 'stored')) {
|
|
67
|
+
throw new InvalidConditionalPutResultError('inconsistent_status');
|
|
68
|
+
}
|
|
69
|
+
const allowLogicalCollision = options?.allowLogicalCollision === true;
|
|
70
|
+
if (status === 'logical_collision'
|
|
71
|
+
&& (!collisionWithAssetId || collisionWithAssetId === expectedAssetId || allowLogicalCollision)) {
|
|
72
|
+
throw new InvalidConditionalPutResultError('invalid_collision');
|
|
73
|
+
}
|
|
74
|
+
if (status === 'stored' && collisionWithAssetId && !allowLogicalCollision) {
|
|
75
|
+
throw new InvalidConditionalPutResultError('collision_bypass');
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
export function supportsAtomicConditionalPut(provider) {
|
|
80
|
+
return typeof provider.putConditional === 'function';
|
|
81
|
+
}
|
|
82
|
+
export function supportsAtomicFrozenConditionalPut(provider) {
|
|
83
|
+
return typeof provider.putFrozenConditional === 'function';
|
|
84
|
+
}
|
|
2
85
|
export class AssetIdMismatchError extends Error {
|
|
3
86
|
claimed;
|
|
4
87
|
actual;
|
|
@@ -12,6 +95,18 @@ export class AssetIdMismatchError extends Error {
|
|
|
12
95
|
export class CapsuleGeneBindingError extends Error {
|
|
13
96
|
constructor() { super('Capsule.gene 必须非空或显式 "ad-hoc" 哨兵 (批注#28/M3-4)'); this.name = 'CapsuleGeneBindingError'; }
|
|
14
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* M3-4 强绑定校验: Capsule.gene 必须非空(否则一条无来源基因的经验会污染选择池).
|
|
100
|
+
* 抽成独立 helper 以便 normalizeForPut(校验落库路径)与 ingestUnverified(冻结落库路径,
|
|
101
|
+
* 绕过 normalizeForPut)共用同一条不变量,不让降级路径把绑定校验漏掉.
|
|
102
|
+
*/
|
|
103
|
+
export function assertCapsuleGeneBinding(asset) {
|
|
104
|
+
if (asset.type !== 'Capsule')
|
|
105
|
+
return;
|
|
106
|
+
const gene = asset.gene;
|
|
107
|
+
if (typeof gene !== 'string' || gene.length === 0)
|
|
108
|
+
throw new CapsuleGeneBindingError();
|
|
109
|
+
}
|
|
15
110
|
/**
|
|
16
111
|
* 落库前规范化(共享给各 provider): 计算/校验 asset_id + 强绑定校验.
|
|
17
112
|
* - 缺 asset_id → 计算填入(verified=false 表示非入参自带).
|
|
@@ -19,11 +114,7 @@ export class CapsuleGeneBindingError extends Error {
|
|
|
19
114
|
* - Capsule.gene 必须非空(M3-4 强绑定).
|
|
20
115
|
*/
|
|
21
116
|
export function normalizeForPut(asset) {
|
|
22
|
-
|
|
23
|
-
const gene = asset.gene;
|
|
24
|
-
if (typeof gene !== 'string' || gene.length === 0)
|
|
25
|
-
throw new CapsuleGeneBindingError();
|
|
26
|
-
}
|
|
117
|
+
assertCapsuleGeneBinding(asset);
|
|
27
118
|
const actual = computeAssetId(asset);
|
|
28
119
|
if (actual === null)
|
|
29
120
|
throw new Error('computeAssetId 失败: 资产非对象');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AssetStoreProvider, AssetRecord } from './provider.js';
|
|
2
|
-
import { ReviewLedger } from './reviewLedger.js';
|
|
2
|
+
import { ReviewLedger, type ReviewRecord } from './reviewLedger.js';
|
|
3
3
|
import { ProvenanceStore } from './provenance.js';
|
|
4
4
|
/**
|
|
5
5
|
* The ReviewLedger CO-LOCATED with a store — its quarantine/approve records live in the SAME dir as the genes.
|
|
@@ -17,4 +17,22 @@ export declare function provenanceStoreForStore(store: AssetStoreProvider): Prov
|
|
|
17
17
|
* quarantined or rejected draft is withheld. A gene with NO provenance record is trusted; hub-ingested genes are
|
|
18
18
|
* withheld until promotion. This mirrors candidateAssembly's trust-first + review-first filters.
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Authoritative pending-review queue: quarantined ledger records, oldest first.
|
|
22
|
+
* Callers that display a human queue MUST start here instead of `store.list`, or a
|
|
23
|
+
* draft sitting past the newest-N cutoff is invisible even though it is still blocked.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pendingReviewRecords(review: ReviewLedger, excludeAssetIds?: ReadonlySet<string>): ReviewRecord[];
|
|
26
|
+
/**
|
|
27
|
+
* Pending Gene queue with the asset store as the type/existence authority.
|
|
28
|
+
*
|
|
29
|
+
* ReviewLedger is intentionally generic because the same sidecar also records
|
|
30
|
+
* AntiGene decisions. A quarantined row can also outlive its asset after a
|
|
31
|
+
* repair or a truncated migration. Human Gene queues must therefore resolve
|
|
32
|
+
* every row through the provider instead of treating the ledger alone as a
|
|
33
|
+
* complete inventory. The lookups are deliberately sequential so a malformed
|
|
34
|
+
* or unexpectedly large ledger cannot fan out an unbounded request burst to a
|
|
35
|
+
* remote provider.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pendingGeneReviewRecords(store: AssetStoreProvider, review: ReviewLedger): Promise<ReviewRecord[]>;
|
|
20
38
|
export declare function listApprovedGenes(store: AssetStoreProvider, review: ReviewLedger, maxGenes: number, provenance?: ProvenanceStore): Promise<AssetRecord[]>;
|
|
@@ -28,14 +28,52 @@ export function provenanceStoreForStore(store) {
|
|
|
28
28
|
* quarantined or rejected draft is withheld. A gene with NO provenance record is trusted; hub-ingested genes are
|
|
29
29
|
* withheld until promotion. This mirrors candidateAssembly's trust-first + review-first filters.
|
|
30
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* Authoritative pending-review queue: quarantined ledger records, oldest first.
|
|
33
|
+
* Callers that display a human queue MUST start here instead of `store.list`, or a
|
|
34
|
+
* draft sitting past the newest-N cutoff is invisible even though it is still blocked.
|
|
35
|
+
*/
|
|
36
|
+
export function pendingReviewRecords(review, excludeAssetIds = new Set()) {
|
|
37
|
+
return review.records()
|
|
38
|
+
.filter((record) => record.state === 'quarantined' && !excludeAssetIds.has(record.assetId))
|
|
39
|
+
.sort((a, b) => a.at.localeCompare(b.at));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Pending Gene queue with the asset store as the type/existence authority.
|
|
43
|
+
*
|
|
44
|
+
* ReviewLedger is intentionally generic because the same sidecar also records
|
|
45
|
+
* AntiGene decisions. A quarantined row can also outlive its asset after a
|
|
46
|
+
* repair or a truncated migration. Human Gene queues must therefore resolve
|
|
47
|
+
* every row through the provider instead of treating the ledger alone as a
|
|
48
|
+
* complete inventory. The lookups are deliberately sequential so a malformed
|
|
49
|
+
* or unexpectedly large ledger cannot fan out an unbounded request burst to a
|
|
50
|
+
* remote provider.
|
|
51
|
+
*/
|
|
52
|
+
export async function pendingGeneReviewRecords(store, review) {
|
|
53
|
+
const pending = pendingReviewRecords(review);
|
|
54
|
+
// LocalJsonlProvider 可直接使用内存索引精确回答,避免 sidecar 变大后逐行等待查询。
|
|
55
|
+
if (store instanceof LocalJsonlProvider) {
|
|
56
|
+
const geneIds = new Set(store.listAll('Gene').map((asset) => asset.asset_id));
|
|
57
|
+
return pending.filter((record) => geneIds.has(record.assetId));
|
|
58
|
+
}
|
|
59
|
+
const genes = [];
|
|
60
|
+
for (const record of pending) {
|
|
61
|
+
const asset = await store.get(record.assetId);
|
|
62
|
+
if (asset?.type === 'Gene')
|
|
63
|
+
genes.push(record);
|
|
64
|
+
}
|
|
65
|
+
return genes;
|
|
66
|
+
}
|
|
31
67
|
export async function listApprovedGenes(store, review, maxGenes, provenance = provenanceStoreForStore(store)) {
|
|
32
68
|
const all = await store.list('Gene', GENE_SCAN_LIMIT);
|
|
33
69
|
const trust = provenance.snapshot();
|
|
70
|
+
const reviewed = review.snapshot();
|
|
34
71
|
const approved = [];
|
|
35
72
|
for (const g of all) {
|
|
36
73
|
if (trust.get(String(g.asset_id))?.trusted === false)
|
|
37
74
|
continue; // hub-untrusted → withhold until promoted
|
|
38
|
-
|
|
75
|
+
const reviewRecord = reviewed.get(String(g.asset_id));
|
|
76
|
+
if (reviewRecord !== undefined && reviewRecord.state !== 'approved')
|
|
39
77
|
continue; // quarantined/rejected draft → withhold
|
|
40
78
|
approved.push(g);
|
|
41
79
|
if (approved.length >= maxGenes)
|
|
@@ -18,13 +18,17 @@ export interface ReviewRecord {
|
|
|
18
18
|
export declare class ReviewLedger {
|
|
19
19
|
private readonly now;
|
|
20
20
|
private readonly path;
|
|
21
|
+
private readonly lockPath;
|
|
21
22
|
private readonly index;
|
|
22
|
-
private
|
|
23
|
+
private fileState;
|
|
23
24
|
constructor(baseDir: string, now?: () => number);
|
|
24
25
|
private static isHuman;
|
|
25
26
|
/** Which record wins for an asset_id: a human decision beats a quarantine; otherwise the later one wins. */
|
|
26
27
|
private static keep;
|
|
27
|
-
private
|
|
28
|
+
private rebuildIndex;
|
|
29
|
+
private refreshUnderLock;
|
|
30
|
+
private withFreshRead;
|
|
31
|
+
private appendUnderLock;
|
|
28
32
|
/** Record a review-state for an asset_id (append-only; the JSONL history is the audit trail). */
|
|
29
33
|
mark(rec: Omit<ReviewRecord, 'at'> & {
|
|
30
34
|
at?: string;
|
|
@@ -50,6 +54,8 @@ export declare class ReviewLedger {
|
|
|
50
54
|
* asset list, so a draft awaiting approval is never missed behind a store-list cutoff.
|
|
51
55
|
*/
|
|
52
56
|
records(): ReviewRecord[];
|
|
57
|
+
/** One linearizable review snapshot for bounded batch readers. */
|
|
58
|
+
snapshot(): ReadonlyMap<string, ReviewRecord>;
|
|
53
59
|
/** No record → approved (default eligible); a record → approved only when its state is 'approved'. */
|
|
54
60
|
isApproved(assetId: string): boolean;
|
|
55
61
|
/**
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Default (no record) = approved/eligible: the only writer of quarantine records is `--distill`; cycle-self-
|
|
10
10
|
// produced and v1-migrated genes are never quarantined, so the explore→prove loop is untouched for them.
|
|
11
|
-
import { appendFileSync, existsSync, readFileSync, mkdirSync, statSync } from 'node:fs';
|
|
12
11
|
import { join, dirname } from 'node:path';
|
|
12
|
+
import { appendUtf8Durable, assertAssetStoreDirectory, ensureAssetStoreDirectory, readUtf8Regular, regularFileFingerprint, withAssetStoreLock, } from './assetStoreStorage.js';
|
|
13
|
+
import { assertTrustSidecarHealthy, parseReviewRecord, parseSidecarJsonl, } from './assetSidecarRecords.js';
|
|
14
|
+
function immutableRecord(record) {
|
|
15
|
+
return Object.freeze({ ...record });
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Append-only JSONL sidecar at <baseDir>/review.jsonl. Default for an asset with NO record = approved (eligible):
|
|
15
19
|
* only auto-distilled drafts are quarantined here; everything else is eligible by default.
|
|
@@ -22,11 +26,14 @@ import { join, dirname } from 'node:path';
|
|
|
22
26
|
export class ReviewLedger {
|
|
23
27
|
now;
|
|
24
28
|
path;
|
|
29
|
+
lockPath;
|
|
25
30
|
index = new Map();
|
|
26
|
-
|
|
31
|
+
fileState = null;
|
|
27
32
|
constructor(baseDir, now = Date.now) {
|
|
28
33
|
this.now = now;
|
|
34
|
+
ensureAssetStoreDirectory(baseDir);
|
|
29
35
|
this.path = join(baseDir, 'review.jsonl');
|
|
36
|
+
this.lockPath = join(baseDir, '.assetstore.lock');
|
|
30
37
|
}
|
|
31
38
|
static isHuman(s) { return s === 'approved' || s === 'rejected'; }
|
|
32
39
|
/** Which record wins for an asset_id: a human decision beats a quarantine; otherwise the later one wins. */
|
|
@@ -37,43 +44,50 @@ export class ReviewLedger {
|
|
|
37
44
|
return r; // human decision always wins (and later human beats earlier)
|
|
38
45
|
return ReviewLedger.isHuman(existing.state) ? existing : r; // a quarantine replaces only a prior quarantine
|
|
39
46
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
const st = statSync(this.path);
|
|
51
|
-
const sig = `${st.mtimeMs}:${st.size}`;
|
|
52
|
-
if (sig === this.sig)
|
|
53
|
-
return; // unchanged since last read → cached index is current
|
|
54
|
-
this.index.clear();
|
|
55
|
-
for (const line of readFileSync(this.path, 'utf8').split('\n')) {
|
|
56
|
-
if (!line.trim())
|
|
57
|
-
continue;
|
|
58
|
-
try {
|
|
59
|
-
const r = JSON.parse(line);
|
|
60
|
-
if (r.assetId)
|
|
61
|
-
this.index.set(r.assetId, ReviewLedger.keep(this.index.get(r.assetId), r));
|
|
47
|
+
rebuildIndex(state) {
|
|
48
|
+
const next = new Map();
|
|
49
|
+
const raw = state === 'missing' ? null : readUtf8Regular(this.path);
|
|
50
|
+
if (raw !== null) {
|
|
51
|
+
const parsed = parseSidecarJsonl(raw, parseReviewRecord);
|
|
52
|
+
assertTrustSidecarHealthy('review', parsed);
|
|
53
|
+
for (const record of parsed.records) {
|
|
54
|
+
const frozen = immutableRecord(record);
|
|
55
|
+
next.set(record.assetId, ReviewLedger.keep(next.get(record.assetId), frozen));
|
|
62
56
|
}
|
|
63
|
-
catch { /* skip corrupt line */ }
|
|
64
57
|
}
|
|
65
|
-
this.
|
|
58
|
+
this.index.clear();
|
|
59
|
+
for (const [assetId, record] of next)
|
|
60
|
+
this.index.set(assetId, record);
|
|
61
|
+
this.fileState = state;
|
|
62
|
+
}
|
|
63
|
+
refreshUnderLock() {
|
|
64
|
+
const state = regularFileFingerprint(this.path);
|
|
65
|
+
if (state !== this.fileState)
|
|
66
|
+
this.rebuildIndex(state);
|
|
67
|
+
}
|
|
68
|
+
withFreshRead(read) {
|
|
69
|
+
assertAssetStoreDirectory(dirname(this.path));
|
|
70
|
+
return withAssetStoreLock(this.lockPath, () => {
|
|
71
|
+
this.refreshUnderLock();
|
|
72
|
+
return read(this.index);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
appendUnderLock(full) {
|
|
76
|
+
appendUtf8Durable(this.path, `${JSON.stringify(full)}\n`);
|
|
77
|
+
const frozen = immutableRecord(full);
|
|
78
|
+
this.index.set(full.assetId, ReviewLedger.keep(this.index.get(full.assetId), frozen));
|
|
79
|
+
this.fileState = regularFileFingerprint(this.path);
|
|
80
|
+
return this.index.get(full.assetId);
|
|
66
81
|
}
|
|
67
82
|
/** Record a review-state for an asset_id (append-only; the JSONL history is the audit trail). */
|
|
68
83
|
mark(rec) {
|
|
69
|
-
this.load();
|
|
70
84
|
const full = { ...rec, at: rec.at ?? new Date(this.now()).toISOString() };
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
assertAssetStoreDirectory(dirname(this.path));
|
|
86
|
+
return withAssetStoreLock(this.lockPath, () => {
|
|
87
|
+
this.refreshUnderLock();
|
|
88
|
+
this.appendUnderLock(full);
|
|
89
|
+
return immutableRecord(full);
|
|
90
|
+
});
|
|
77
91
|
}
|
|
78
92
|
/** Quarantine an auto-distilled draft: its strategy must not enter a real run until reviewed. */
|
|
79
93
|
quarantine(assetId, reason = 'auto-distilled — review before use') {
|
|
@@ -87,9 +101,19 @@ export class ReviewLedger {
|
|
|
87
101
|
* approval is inert. Returns the surviving record (the existing decision, or the new quarantine).
|
|
88
102
|
*/
|
|
89
103
|
quarantineIfAbsent(assetId, reason = 'auto-distilled — review before use') {
|
|
90
|
-
this.
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
assertAssetStoreDirectory(dirname(this.path));
|
|
105
|
+
return withAssetStoreLock(this.lockPath, () => {
|
|
106
|
+
this.refreshUnderLock();
|
|
107
|
+
const existing = this.index.get(assetId);
|
|
108
|
+
if (existing)
|
|
109
|
+
return existing;
|
|
110
|
+
return this.appendUnderLock({
|
|
111
|
+
assetId,
|
|
112
|
+
state: 'quarantined',
|
|
113
|
+
reason,
|
|
114
|
+
at: new Date(this.now()).toISOString(),
|
|
115
|
+
});
|
|
116
|
+
});
|
|
93
117
|
}
|
|
94
118
|
/** Explicit, audited approval (who/why) — flips a draft to eligible. */
|
|
95
119
|
approve(assetId, by, reason) {
|
|
@@ -100,8 +124,7 @@ export class ReviewLedger {
|
|
|
100
124
|
return this.mark({ assetId, state: 'rejected', by, reason });
|
|
101
125
|
}
|
|
102
126
|
get(assetId) {
|
|
103
|
-
this.
|
|
104
|
-
return this.index.get(assetId) ?? null;
|
|
127
|
+
return this.withFreshRead((index) => index.get(assetId) ?? null);
|
|
105
128
|
}
|
|
106
129
|
/**
|
|
107
130
|
* Every recorded review decision (resolved, reload-aware). The authoritative source of which assets are
|
|
@@ -109,14 +132,18 @@ export class ReviewLedger {
|
|
|
109
132
|
* asset list, so a draft awaiting approval is never missed behind a store-list cutoff.
|
|
110
133
|
*/
|
|
111
134
|
records() {
|
|
112
|
-
this.
|
|
113
|
-
|
|
135
|
+
return [...this.snapshot().values()];
|
|
136
|
+
}
|
|
137
|
+
/** One linearizable review snapshot for bounded batch readers. */
|
|
138
|
+
snapshot() {
|
|
139
|
+
return this.withFreshRead((index) => new Map(index));
|
|
114
140
|
}
|
|
115
141
|
/** No record → approved (default eligible); a record → approved only when its state is 'approved'. */
|
|
116
142
|
isApproved(assetId) {
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
|
|
143
|
+
return this.withFreshRead((index) => {
|
|
144
|
+
const r = index.get(assetId);
|
|
145
|
+
return r ? r.state === 'approved' : true;
|
|
146
|
+
});
|
|
120
147
|
}
|
|
121
148
|
/**
|
|
122
149
|
* True only when a human approval record exists. Safety-sensitive consumers such as AntiGene warning
|
|
@@ -124,7 +151,6 @@ export class ReviewLedger {
|
|
|
124
151
|
* negative-memory asset must never inherit the ledger's backward-compatible "no record = eligible" default.
|
|
125
152
|
*/
|
|
126
153
|
isExplicitlyApproved(assetId) {
|
|
127
|
-
this.
|
|
128
|
-
return this.index.get(assetId)?.state === 'approved';
|
|
154
|
+
return this.withFreshRead((index) => index.get(assetId)?.state === 'approved');
|
|
129
155
|
}
|
|
130
156
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AssetKind, AssetRecord, AssetStoreProvider, ConditionalPutOptions, ConditionalPutResult, PutResult, SearchQuery } from './provider.js';
|
|
2
|
+
export declare class UnionReadStore implements AssetStoreProvider {
|
|
3
|
+
private readonly primary;
|
|
4
|
+
private readonly readOnly;
|
|
5
|
+
readonly putConditional?: (asset: AssetRecord, options?: ConditionalPutOptions) => Promise<ConditionalPutResult>;
|
|
6
|
+
readonly putBundle?: (assets: readonly AssetRecord[]) => Promise<PutResult[]>;
|
|
7
|
+
readonly putFrozen?: (record: AssetRecord) => Promise<PutResult>;
|
|
8
|
+
readonly putFrozenConditional?: (record: AssetRecord, options?: ConditionalPutOptions) => Promise<ConditionalPutResult>;
|
|
9
|
+
readonly findByLogicalId?: (id: string, limit?: number, kind?: AssetKind) => Promise<AssetRecord[]>;
|
|
10
|
+
/**
|
|
11
|
+
* @param primary the writable pool this engine owns; every mutation lands here
|
|
12
|
+
* @param readOnly additional pools to read, in precedence order after `primary`
|
|
13
|
+
*/
|
|
14
|
+
constructor(primary: AssetStoreProvider, readOnly: readonly AssetStoreProvider[]);
|
|
15
|
+
private get sources();
|
|
16
|
+
/**
|
|
17
|
+
* Merge one read across every source: deduplicate by asset_id with earlier sources winning, then fill
|
|
18
|
+
* the result by {@link interleave} so every source gets a fair share of the cap.
|
|
19
|
+
*/
|
|
20
|
+
private merged;
|
|
21
|
+
put(asset: AssetRecord): Promise<PutResult>;
|
|
22
|
+
get(assetId: string): Promise<AssetRecord | null>;
|
|
23
|
+
list(kind?: AssetKind, limit?: number): Promise<AssetRecord[]>;
|
|
24
|
+
search(q: SearchQuery): Promise<AssetRecord[]>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Read-union over one writable pool plus any number of read-only ones.
|
|
2
|
+
//
|
|
3
|
+
// Why a provider and not a helper: every evidence consumer already takes an AssetStoreProvider and calls
|
|
4
|
+
// `search`/`list` on it — aggregateLearningHistory, assessGenePublishEvidence, candidateAssembly,
|
|
5
|
+
// probationWouldPromote. Widening what they can see by threading a second store through each of them
|
|
6
|
+
// would fork four call chains and leave the next consumer to remember. Composing at the provider seam
|
|
7
|
+
// instead means they are untouched: whoever builds the store decides how wide the evidence pool is.
|
|
8
|
+
//
|
|
9
|
+
// Writes go to the primary and ONLY the primary. Reads merge, deduplicating by asset_id — records are
|
|
10
|
+
// content-addressed, so the same asset present in two pools is the same asset, and primary wins so a
|
|
11
|
+
// locally-verified record is never shadowed by a foreign copy.
|
|
12
|
+
const DEFAULT_LIMIT = 1000;
|
|
13
|
+
/**
|
|
14
|
+
* Take from the queues one at a time in source order until `limit` is reached.
|
|
15
|
+
*
|
|
16
|
+
* Concatenating instead would let a mature primary fill every slot on its own — at the ~1000-record
|
|
17
|
+
* limits the real callers pass (candidateAssembly, recall, reviewFilter), a local pool that size returns
|
|
18
|
+
* ZERO foreign records and the union silently degrades back to the single-store blind spot it exists to
|
|
19
|
+
* fix. Interleaving also refills: a source that runs out yields its remaining slots to the others, so a
|
|
20
|
+
* small foreign pool never costs the result its cap.
|
|
21
|
+
*/
|
|
22
|
+
function interleave(queues, limit) {
|
|
23
|
+
const out = [];
|
|
24
|
+
for (let cursor = 0; out.length < limit; cursor += 1) {
|
|
25
|
+
let advanced = false;
|
|
26
|
+
for (const queue of queues) {
|
|
27
|
+
const record = queue[cursor];
|
|
28
|
+
if (record === undefined)
|
|
29
|
+
continue;
|
|
30
|
+
advanced = true;
|
|
31
|
+
out.push(record);
|
|
32
|
+
if (out.length >= limit)
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
if (!advanced)
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
export class UnionReadStore {
|
|
41
|
+
primary;
|
|
42
|
+
readOnly;
|
|
43
|
+
// Mirrored as assigned-in-constructor properties rather than plain methods on purpose: the optional
|
|
44
|
+
// write capabilities are feature-DETECTED by callers (`supportsAtomicConditionalPut`, and
|
|
45
|
+
// `ingestUnverified`'s `putFrozen` probe). A method declared on the class is always present, so a
|
|
46
|
+
// union wrapping a primary that lacks the capability would answer "supported" and then throw — the
|
|
47
|
+
// undeclared-exception failure those probes exist to prevent. Assigning only when the primary has it
|
|
48
|
+
// keeps the union exactly as capable as the pool it writes to.
|
|
49
|
+
putConditional;
|
|
50
|
+
putBundle;
|
|
51
|
+
putFrozen;
|
|
52
|
+
putFrozenConditional;
|
|
53
|
+
// Conditional for the same reason, on the read side: cliContracts.findLogicalAsset and
|
|
54
|
+
// assetLineage.resolveAsset feature-detect this, and when it is absent fall back to a wide scan that
|
|
55
|
+
// REPORTS truncation. Answering on behalf of a source that cannot resolve logical ids would strand
|
|
56
|
+
// those callers on a narrow answer with no truncation signal — a silent "not found" where they would
|
|
57
|
+
// otherwise have said "lookup is truncated; use an exact asset_id".
|
|
58
|
+
findByLogicalId;
|
|
59
|
+
/**
|
|
60
|
+
* @param primary the writable pool this engine owns; every mutation lands here
|
|
61
|
+
* @param readOnly additional pools to read, in precedence order after `primary`
|
|
62
|
+
*/
|
|
63
|
+
constructor(primary, readOnly) {
|
|
64
|
+
this.primary = primary;
|
|
65
|
+
this.readOnly = readOnly;
|
|
66
|
+
if ([primary, ...readOnly].every((source) => source.findByLogicalId)) {
|
|
67
|
+
this.findByLogicalId = (id, limit = DEFAULT_LIMIT, kind) => this.merged((source) => source.findByLogicalId(id, limit, kind), limit);
|
|
68
|
+
}
|
|
69
|
+
const { putBundle, putConditional, putFrozen, putFrozenConditional } = primary;
|
|
70
|
+
if (putBundle)
|
|
71
|
+
this.putBundle = (assets) => putBundle.call(primary, assets);
|
|
72
|
+
if (putConditional)
|
|
73
|
+
this.putConditional = (asset, options) => putConditional.call(primary, asset, options);
|
|
74
|
+
if (putFrozen)
|
|
75
|
+
this.putFrozen = (record) => putFrozen.call(primary, record);
|
|
76
|
+
if (putFrozenConditional) {
|
|
77
|
+
this.putFrozenConditional = (record, options) => putFrozenConditional.call(primary, record, options);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
get sources() {
|
|
81
|
+
return [this.primary, ...this.readOnly];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Merge one read across every source: deduplicate by asset_id with earlier sources winning, then fill
|
|
85
|
+
* the result by {@link interleave} so every source gets a fair share of the cap.
|
|
86
|
+
*/
|
|
87
|
+
async merged(read, limit) {
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
const queues = [];
|
|
90
|
+
for (const source of this.sources) {
|
|
91
|
+
const unique = [];
|
|
92
|
+
for (const record of await read(source)) {
|
|
93
|
+
if (seen.has(record.asset_id))
|
|
94
|
+
continue;
|
|
95
|
+
seen.add(record.asset_id);
|
|
96
|
+
unique.push(record);
|
|
97
|
+
}
|
|
98
|
+
queues.push(unique);
|
|
99
|
+
}
|
|
100
|
+
return interleave(queues, limit);
|
|
101
|
+
}
|
|
102
|
+
async put(asset) {
|
|
103
|
+
return this.primary.put(asset);
|
|
104
|
+
}
|
|
105
|
+
async get(assetId) {
|
|
106
|
+
for (const source of this.sources) {
|
|
107
|
+
const found = await source.get(assetId);
|
|
108
|
+
if (found)
|
|
109
|
+
return found;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
async list(kind, limit = DEFAULT_LIMIT) {
|
|
114
|
+
return this.merged((source) => source.list(kind, limit), limit);
|
|
115
|
+
}
|
|
116
|
+
async search(q) {
|
|
117
|
+
return this.merged((source) => source.search(q), q.limit ?? DEFAULT_LIMIT);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -70,6 +70,8 @@ export interface AntiGeneBenchmarkOptions {
|
|
|
70
70
|
minFailureDelta?: number;
|
|
71
71
|
now?: () => number;
|
|
72
72
|
eventsPath?: string;
|
|
73
|
+
/** Apply the pre-IDF semantic scorer consistently to both A/B arms. */
|
|
74
|
+
disableSemanticIdf?: boolean;
|
|
73
75
|
}
|
|
74
76
|
export interface AntiGeneBenchmarkDeps {
|
|
75
77
|
store: AssetStoreProvider;
|
|
@@ -79,7 +79,7 @@ function executeFor(outcome) {
|
|
|
79
79
|
},
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
async function runOneArm(arm, task, source, sourceReview, rootDir, now) {
|
|
82
|
+
async function runOneArm(arm, task, source, sourceReview, rootDir, now, disableSemanticIdf) {
|
|
83
83
|
const armDir = join(rootDir, arm, task.id);
|
|
84
84
|
const store = new LocalJsonlProvider(join(armDir, 'assets'));
|
|
85
85
|
const review = new ReviewLedger(join(armDir, 'assets'), now);
|
|
@@ -105,6 +105,7 @@ async function runOneArm(arm, task, source, sourceReview, rootDir, now) {
|
|
|
105
105
|
execute: executeFor(spec),
|
|
106
106
|
review,
|
|
107
107
|
consumePendingSignals: false,
|
|
108
|
+
...(disableSemanticIdf ? { disableSemanticIdf: true } : {}),
|
|
108
109
|
};
|
|
109
110
|
const result = await runEvolutionCycle(engine, store, opts);
|
|
110
111
|
const observed = observedWarningIds(result.decision);
|
|
@@ -168,8 +169,8 @@ export async function runAntiGeneBenchmark(suite, deps, options = {}) {
|
|
|
168
169
|
for (const task of suite.tasks) {
|
|
169
170
|
taskResults.push({
|
|
170
171
|
taskId: task.id,
|
|
171
|
-
baseline: await runOneArm('baseline', task, deps.store, sourceReview, rootDir, now),
|
|
172
|
-
antiGene: await runOneArm('antiGene', task, deps.store, sourceReview, rootDir, now),
|
|
172
|
+
baseline: await runOneArm('baseline', task, deps.store, sourceReview, rootDir, now, options.disableSemanticIdf === true),
|
|
173
|
+
antiGene: await runOneArm('antiGene', task, deps.store, sourceReview, rootDir, now, options.disableSemanticIdf === true),
|
|
173
174
|
});
|
|
174
175
|
}
|
|
175
176
|
const report = buildReport(suite, taskResults, minSamples, minFailureDelta);
|
|
@@ -75,6 +75,8 @@ export interface AntiGeneRolloutOptions {
|
|
|
75
75
|
minFailureDelta?: number;
|
|
76
76
|
now?: () => number;
|
|
77
77
|
eventsPath?: string;
|
|
78
|
+
/** Apply the pre-IDF semantic scorer consistently to both A/B arms. */
|
|
79
|
+
disableSemanticIdf?: boolean;
|
|
78
80
|
}
|
|
79
81
|
export declare function runAntiGeneRollout(suite: AntiGeneRolloutSuite, deps: AntiGeneRolloutDeps, options?: AntiGeneRolloutOptions): Promise<AntiGeneRolloutReport>;
|
|
80
82
|
export declare function writeAntiGeneRolloutResult(report: AntiGeneRolloutReport, eventsPath: string, now?: () => number): Promise<void>;
|
|
@@ -82,7 +82,7 @@ function scoreFromResult(result, status) {
|
|
|
82
82
|
const score = result.capsule?.outcome?.score;
|
|
83
83
|
return typeof score === 'number' && Number.isFinite(score) ? score : status === 'success' ? 0.9 : 0.2;
|
|
84
84
|
}
|
|
85
|
-
async function runOneArm(arm, task, source, sourceReview, makeExecute, rootDir, now) {
|
|
85
|
+
async function runOneArm(arm, task, source, sourceReview, makeExecute, rootDir, now, disableSemanticIdf) {
|
|
86
86
|
const armDir = join(rootDir, arm, task.id);
|
|
87
87
|
const store = new LocalJsonlProvider(join(armDir, 'assets'));
|
|
88
88
|
const review = new ReviewLedger(join(armDir, 'assets'), now);
|
|
@@ -107,6 +107,7 @@ async function runOneArm(arm, task, source, sourceReview, makeExecute, rootDir,
|
|
|
107
107
|
execute: makeExecute({ arm, task, store, review, armDir }),
|
|
108
108
|
review,
|
|
109
109
|
consumePendingSignals: false,
|
|
110
|
+
...(disableSemanticIdf ? { disableSemanticIdf: true } : {}),
|
|
110
111
|
};
|
|
111
112
|
const result = await runEvolutionCycle(engine, store, opts);
|
|
112
113
|
const status = statusFromResult(result);
|
|
@@ -183,8 +184,8 @@ export async function runAntiGeneRollout(suite, deps, options = {}) {
|
|
|
183
184
|
for (const task of suite.tasks) {
|
|
184
185
|
taskResults.push({
|
|
185
186
|
taskId: task.id,
|
|
186
|
-
baseline: await runOneArm('baseline', task, deps.store, sourceReview, deps.makeExecute, rootDir, now),
|
|
187
|
-
antiGene: await runOneArm('antiGene', task, deps.store, sourceReview, deps.makeExecute, rootDir, now),
|
|
187
|
+
baseline: await runOneArm('baseline', task, deps.store, sourceReview, deps.makeExecute, rootDir, now, options.disableSemanticIdf === true),
|
|
188
|
+
antiGene: await runOneArm('antiGene', task, deps.store, sourceReview, deps.makeExecute, rootDir, now, options.disableSemanticIdf === true),
|
|
188
189
|
});
|
|
189
190
|
}
|
|
190
191
|
const report = buildReport(suite, taskResults, minSamples, minFailureDelta);
|
|
@@ -2,4 +2,6 @@ export * from './benchmark.js';
|
|
|
2
2
|
export * from './thesis.js';
|
|
3
3
|
export * from './evolutionThesisSolver.js';
|
|
4
4
|
export * from './antiGeneBenchmark.js';
|
|
5
|
-
export * from './antiGeneRollout.js';
|
|
5
|
+
export * from './antiGeneRollout.js';
|
|
6
|
+
export * from './triggerShift.js';
|
|
7
|
+
export * from './selectionFlatAbstention.js';
|
package/dist/benchmark/index.js
CHANGED
|
@@ -2,4 +2,6 @@ export * from './benchmark.js';
|
|
|
2
2
|
export * from './thesis.js';
|
|
3
3
|
export * from './evolutionThesisSolver.js';
|
|
4
4
|
export * from './antiGeneBenchmark.js';
|
|
5
|
-
export * from './antiGeneRollout.js';
|
|
5
|
+
export * from './antiGeneRollout.js';
|
|
6
|
+
export * from './triggerShift.js';
|
|
7
|
+
export * from './selectionFlatAbstention.js';
|