@akemi-mio/creativity 0.1.0 → 0.2.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/ConceptMixer.d.ts +17 -1
- package/dist/ConceptMixer.js +36 -3
- package/dist/CreativityPrompt.d.ts +5 -1
- package/dist/CreativityPrompt.js +36 -1
- package/dist/DrizzleFeedbackEventStore.d.ts +22 -0
- package/dist/DrizzleFeedbackEventStore.js +101 -0
- package/dist/ExperimentRunStore.d.ts +106 -0
- package/dist/ExperimentRunStore.js +160 -0
- package/dist/FeedbackEvents.d.ts +86 -0
- package/dist/FeedbackEvents.js +133 -0
- package/dist/FeedbackLearning.d.ts +195 -0
- package/dist/FeedbackLearning.js +456 -0
- package/dist/FeedbackRollout.d.ts +72 -0
- package/dist/FeedbackRollout.js +136 -0
- package/dist/FeedbackRuntime.d.ts +37 -0
- package/dist/FeedbackRuntime.js +72 -0
- package/dist/HypothesisGenerator.d.ts +4 -1
- package/dist/HypothesisGenerator.js +9 -7
- package/dist/IdeaGenerator.d.ts +7 -1
- package/dist/IdeaGenerator.js +8 -4
- package/dist/InsightSamplingExperiment.d.ts +82 -0
- package/dist/InsightSamplingExperiment.js +309 -0
- package/dist/JsonlFeedbackEventStore.d.ts +32 -0
- package/dist/JsonlFeedbackEventStore.js +79 -0
- package/dist/MechanismGraph.d.ts +46 -0
- package/dist/MechanismGraph.js +76 -0
- package/dist/MechanismRegistry.d.ts +118 -0
- package/dist/MechanismRegistry.js +233 -0
- package/dist/WhenThenRunner.d.ts +135 -0
- package/dist/WhenThenRunner.js +358 -0
- package/dist/ingestFeedbackEvents.d.ts +32 -0
- package/dist/ingestFeedbackEvents.js +42 -0
- package/dist/types.d.ts +26 -1
- package/dist/types.js +15 -0
- package/package.json +67 -3
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhenThenRunner — 声明式 WHEN/THEN 受限 DSL 运行器(机制涌现 P2a,定案 D5/R5/A2)
|
|
3
|
+
*
|
|
4
|
+
* 候选执行不写任意代码:DSL 查表求值(阈值比较 + 字段存在性 + 枚举匹配,禁 JS eval)。
|
|
5
|
+
* 步骤依赖执行 + 显式 named outputs(无隐式上下文注入)。
|
|
6
|
+
* resourceBudget 编译为 CapabilityToken.constraints。
|
|
7
|
+
* 求值器输入输出全白名单序列化(JSON number/boolean/string)。
|
|
8
|
+
* 条件字符串长度上限,解析失败 = fail-closed(步骤跳过 + 记 guardrail 事件,不中断实验、不泄漏沙箱内部)。
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Token constraints — structural alias for CapabilityToken['constraints'].
|
|
12
|
+
* Defined locally to avoid a cross-package type dependency in the build (tsconfig.build.json is allowlist-based).
|
|
13
|
+
*/
|
|
14
|
+
export interface TokenConstraints {
|
|
15
|
+
maxCalls?: number;
|
|
16
|
+
maxTokens?: number;
|
|
17
|
+
expiryMs?: number;
|
|
18
|
+
allowedValues?: Record<string, string[]>;
|
|
19
|
+
}
|
|
20
|
+
/** 比较操作符(白名单,禁自由表达式) */
|
|
21
|
+
export type CompareOp = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'exists' | 'notExists';
|
|
22
|
+
/** WHEN 条件(查表求值,无 JS eval) */
|
|
23
|
+
export interface WhenCondition {
|
|
24
|
+
/** 数据源路径(点分,如 `metrics.latency` 或 `capabilities.publishing.outcome`) */
|
|
25
|
+
field: string;
|
|
26
|
+
op: CompareOp;
|
|
27
|
+
/** 比较值(number/boolean/string 白名单;in/nin 时为 string[]) */
|
|
28
|
+
value?: number | boolean | string | string[];
|
|
29
|
+
}
|
|
30
|
+
/** THEN 动作 */
|
|
31
|
+
export interface ThenAction {
|
|
32
|
+
/** 步骤 id(图内唯一) */
|
|
33
|
+
id: string;
|
|
34
|
+
/** capability id(执行时 resolve) */
|
|
35
|
+
capabilityId: string;
|
|
36
|
+
/** 显式 named inputs:来自上游 named outputs 或初始 context 的白名单路径 */
|
|
37
|
+
inputs?: Record<string, string>;
|
|
38
|
+
/** 显式 named outputs:本步骤产出名(下游可引用) */
|
|
39
|
+
outputs?: string[];
|
|
40
|
+
/** 资源预算(编译为 CapabilityToken.constraints) */
|
|
41
|
+
resourceBudget?: ResourceBudgetSpec;
|
|
42
|
+
/** 依赖的上游步骤 id(显式 DAG,无隐式顺序) */
|
|
43
|
+
dependsOn?: string[];
|
|
44
|
+
/** WHEN 条件为空则无条件执行;条件不满足 = 本步骤跳过 */
|
|
45
|
+
when?: WhenCondition[];
|
|
46
|
+
}
|
|
47
|
+
/** 单步资源预算 */
|
|
48
|
+
export interface ResourceBudgetSpec {
|
|
49
|
+
maxCalls?: number;
|
|
50
|
+
maxTokens?: number;
|
|
51
|
+
expiryMs?: number;
|
|
52
|
+
allowedValues?: Record<string, string[]>;
|
|
53
|
+
}
|
|
54
|
+
/** 完整 WHEN/THEN 规则(一个候选实验的声明式执行计划) */
|
|
55
|
+
export interface WhenThenRule {
|
|
56
|
+
id: string;
|
|
57
|
+
/** 假设 id(回溯) */
|
|
58
|
+
hypothesisId?: string;
|
|
59
|
+
/** 规则描述 */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** 步骤列表(依赖 DAG) */
|
|
62
|
+
steps: ThenAction[];
|
|
63
|
+
/** 规则级资源预算(步骤级覆盖规则级) */
|
|
64
|
+
resourceBudget?: ResourceBudgetSpec;
|
|
65
|
+
}
|
|
66
|
+
/** 条件字符串长度上限(防超长注入) */
|
|
67
|
+
export declare const MAX_CONDITION_FIELD_LEN = 128;
|
|
68
|
+
export declare const MAX_CONDITION_STRING_VALUE_LEN = 512;
|
|
69
|
+
export declare const MAX_CONDITION_LIST_LEN = 32;
|
|
70
|
+
/** 按点分路径从 context 取值;中间路径不存在返回 undefined。不深入数组索引(防原型链)。 */
|
|
71
|
+
export declare function lookupField(context: Record<string, unknown>, field: string): unknown;
|
|
72
|
+
/** 求值单个 WHEN 条件;解析失败(非法字段/值/操作符)返回 null(调用方 fail-closed)。 */
|
|
73
|
+
export declare function evaluateCondition(context: Record<string, unknown>, cond: WhenCondition): boolean | null;
|
|
74
|
+
/** 求值全部 WHEN 条件(AND);任一解析失败返回 null(fail-closed)。 */
|
|
75
|
+
export declare function evaluateWhen(context: Record<string, unknown>, when: WhenCondition[] | undefined): boolean | null;
|
|
76
|
+
/** 把 ResourceBudgetSpec 编译为 CapabilityToken.constraints(白名单字段)。 */
|
|
77
|
+
export declare function compileBudgetToConstraints(budget: ResourceBudgetSpec | undefined, ruleBudget?: ResourceBudgetSpec): TokenConstraints | undefined;
|
|
78
|
+
/** 单步执行输入(全白名单序列化) */
|
|
79
|
+
export interface StepExecInput {
|
|
80
|
+
stepId: string;
|
|
81
|
+
capabilityId: string;
|
|
82
|
+
/** 显式 named inputs(路径解析后的值) */
|
|
83
|
+
inputs: Record<string, unknown>;
|
|
84
|
+
/** 编译后的 token constraints */
|
|
85
|
+
constraints?: TokenConstraints;
|
|
86
|
+
}
|
|
87
|
+
/** 单步执行结果 */
|
|
88
|
+
export interface StepExecResult {
|
|
89
|
+
success: boolean;
|
|
90
|
+
/** 显式 named outputs(本步骤产出) */
|
|
91
|
+
outputs?: Record<string, unknown>;
|
|
92
|
+
error?: string;
|
|
93
|
+
}
|
|
94
|
+
/** 步骤执行器接口(由 CreativityExecutor / CapabilityService 实现) */
|
|
95
|
+
export type StepExecutor = (input: StepExecInput) => Promise<StepExecResult>;
|
|
96
|
+
/** 运行器事件(guardrail 事件 sink) */
|
|
97
|
+
export interface RunnerEvent {
|
|
98
|
+
event_type: 'whenthen.step.skipped' | 'whenthen.step.failed' | 'whenthen.rule.complete' | 'whenthen.rule.failed';
|
|
99
|
+
rule_id: string;
|
|
100
|
+
step_id?: string;
|
|
101
|
+
/** 公开原因(不泄漏沙箱内部) */
|
|
102
|
+
reason?: string;
|
|
103
|
+
outcome?: string;
|
|
104
|
+
timestamp: number;
|
|
105
|
+
}
|
|
106
|
+
export type RunnerEventSink = (ev: RunnerEvent) => void;
|
|
107
|
+
/** 拓扑排序步骤;环路/未知依赖返回 null(fail-closed)。 */
|
|
108
|
+
export declare function topologicalOrder(steps: readonly ThenAction[]): ThenAction[] | null;
|
|
109
|
+
export interface RunRuleResult {
|
|
110
|
+
success: boolean;
|
|
111
|
+
/** 各步骤 named outputs 汇总(key = stepId.outputName 或 outputName) */
|
|
112
|
+
namedOutputs: Record<string, unknown>;
|
|
113
|
+
/** 各步骤状态 */
|
|
114
|
+
steps: Array<{
|
|
115
|
+
id: string;
|
|
116
|
+
status: 'ok' | 'skipped' | 'failed' | 'blocked';
|
|
117
|
+
reason?: string;
|
|
118
|
+
outputs?: Record<string, unknown>;
|
|
119
|
+
}>;
|
|
120
|
+
events: RunnerEvent[];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 执行一条 WHEN/THEN 规则。
|
|
124
|
+
*
|
|
125
|
+
* - 按 DAG 拓扑顺序调度
|
|
126
|
+
* - 每步先求值 WHEN(解析失败 → fail-closed skip + guardrail 事件)
|
|
127
|
+
* - 输入从初始 context + 上游 named outputs 显式解析(无隐式注入)
|
|
128
|
+
* - 预算编译为 CapabilityToken.constraints 传给 executor
|
|
129
|
+
* - 任一步失败不中断实验:后续依赖步骤 blocked,独立步骤继续
|
|
130
|
+
*/
|
|
131
|
+
export declare function runWhenThenRule(rule: WhenThenRule, context: Record<string, unknown>, executeStep: StepExecutor, options?: {
|
|
132
|
+
/** 初始 named outputs(可选);与 context 不同:context 只用于 WHEN 求值 */
|
|
133
|
+
initialOutputs?: Record<string, unknown>;
|
|
134
|
+
onEvent?: RunnerEventSink;
|
|
135
|
+
}): Promise<RunRuleResult>;
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WhenThenRunner — 声明式 WHEN/THEN 受限 DSL 运行器(机制涌现 P2a,定案 D5/R5/A2)
|
|
4
|
+
*
|
|
5
|
+
* 候选执行不写任意代码:DSL 查表求值(阈值比较 + 字段存在性 + 枚举匹配,禁 JS eval)。
|
|
6
|
+
* 步骤依赖执行 + 显式 named outputs(无隐式上下文注入)。
|
|
7
|
+
* resourceBudget 编译为 CapabilityToken.constraints。
|
|
8
|
+
* 求值器输入输出全白名单序列化(JSON number/boolean/string)。
|
|
9
|
+
* 条件字符串长度上限,解析失败 = fail-closed(步骤跳过 + 记 guardrail 事件,不中断实验、不泄漏沙箱内部)。
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MAX_CONDITION_LIST_LEN = exports.MAX_CONDITION_STRING_VALUE_LEN = exports.MAX_CONDITION_FIELD_LEN = void 0;
|
|
13
|
+
exports.lookupField = lookupField;
|
|
14
|
+
exports.evaluateCondition = evaluateCondition;
|
|
15
|
+
exports.evaluateWhen = evaluateWhen;
|
|
16
|
+
exports.compileBudgetToConstraints = compileBudgetToConstraints;
|
|
17
|
+
exports.topologicalOrder = topologicalOrder;
|
|
18
|
+
exports.runWhenThenRule = runWhenThenRule;
|
|
19
|
+
// ══════════════════════════════════════════
|
|
20
|
+
// 求值器(白名单查表,禁 eval)
|
|
21
|
+
// ══════════════════════════════════════════
|
|
22
|
+
/** 条件字符串长度上限(防超长注入) */
|
|
23
|
+
exports.MAX_CONDITION_FIELD_LEN = 128;
|
|
24
|
+
exports.MAX_CONDITION_STRING_VALUE_LEN = 512;
|
|
25
|
+
exports.MAX_CONDITION_LIST_LEN = 32;
|
|
26
|
+
function isScalar(v) {
|
|
27
|
+
return typeof v === 'number' || typeof v === 'boolean' || typeof v === 'string';
|
|
28
|
+
}
|
|
29
|
+
/** 按点分路径从 context 取值;中间路径不存在返回 undefined。不深入数组索引(防原型链)。 */
|
|
30
|
+
function lookupField(context, field) {
|
|
31
|
+
if (typeof field !== 'string' || field.length === 0 || field.length > exports.MAX_CONDITION_FIELD_LEN)
|
|
32
|
+
return undefined;
|
|
33
|
+
// 禁原型链:字段不得含 `__proto__` / `constructor` / `prototype`
|
|
34
|
+
if (field.includes('__proto__') || field.includes('constructor') || field.includes('prototype'))
|
|
35
|
+
return undefined;
|
|
36
|
+
const parts = field.split('.');
|
|
37
|
+
let cur = context;
|
|
38
|
+
for (const p of parts) {
|
|
39
|
+
if (cur === null || typeof cur !== 'object' || Array.isArray(cur))
|
|
40
|
+
return undefined;
|
|
41
|
+
const obj = cur;
|
|
42
|
+
if (!Object.prototype.hasOwnProperty.call(obj, p))
|
|
43
|
+
return undefined;
|
|
44
|
+
cur = obj[p];
|
|
45
|
+
}
|
|
46
|
+
return cur;
|
|
47
|
+
}
|
|
48
|
+
/** 求值单个 WHEN 条件;解析失败(非法字段/值/操作符)返回 null(调用方 fail-closed)。 */
|
|
49
|
+
function evaluateCondition(context, cond) {
|
|
50
|
+
if (!cond || typeof cond !== 'object')
|
|
51
|
+
return null;
|
|
52
|
+
const { field, op, value } = cond;
|
|
53
|
+
if (typeof field !== 'string' || field.length === 0 || field.length > exports.MAX_CONDITION_FIELD_LEN)
|
|
54
|
+
return null;
|
|
55
|
+
if (field.includes('__proto__') || field.includes('constructor') || field.includes('prototype'))
|
|
56
|
+
return null;
|
|
57
|
+
const actual = lookupField(context, field);
|
|
58
|
+
switch (op) {
|
|
59
|
+
case 'exists':
|
|
60
|
+
return actual !== undefined && actual !== null;
|
|
61
|
+
case 'notExists':
|
|
62
|
+
return actual === undefined || actual === null;
|
|
63
|
+
case 'eq':
|
|
64
|
+
case 'neq':
|
|
65
|
+
case 'gt':
|
|
66
|
+
case 'gte':
|
|
67
|
+
case 'lt':
|
|
68
|
+
case 'lte': {
|
|
69
|
+
if (!isScalar(value))
|
|
70
|
+
return null;
|
|
71
|
+
if (!isScalar(actual))
|
|
72
|
+
return false;
|
|
73
|
+
switch (op) {
|
|
74
|
+
case 'eq':
|
|
75
|
+
return actual === value;
|
|
76
|
+
case 'neq':
|
|
77
|
+
return actual !== value;
|
|
78
|
+
case 'gt':
|
|
79
|
+
return typeof actual === 'number' && typeof value === 'number' && actual > value;
|
|
80
|
+
case 'gte':
|
|
81
|
+
return typeof actual === 'number' && typeof value === 'number' && actual >= value;
|
|
82
|
+
case 'lt':
|
|
83
|
+
return typeof actual === 'number' && typeof value === 'number' && actual < value;
|
|
84
|
+
case 'lte':
|
|
85
|
+
return typeof actual === 'number' && typeof value === 'number' && actual <= value;
|
|
86
|
+
default:
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
case 'in':
|
|
91
|
+
case 'nin': {
|
|
92
|
+
if (!Array.isArray(value) || value.length > exports.MAX_CONDITION_LIST_LEN)
|
|
93
|
+
return null;
|
|
94
|
+
if (!value.every((v) => typeof v === 'string' && v.length <= exports.MAX_CONDITION_STRING_VALUE_LEN))
|
|
95
|
+
return null;
|
|
96
|
+
if (!isScalar(actual))
|
|
97
|
+
return false;
|
|
98
|
+
const hit = value.includes(actual);
|
|
99
|
+
return op === 'in' ? hit : !hit;
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
// 未知操作符 = 解析失败
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** 求值全部 WHEN 条件(AND);任一解析失败返回 null(fail-closed)。 */
|
|
107
|
+
function evaluateWhen(context, when) {
|
|
108
|
+
if (!when || when.length === 0)
|
|
109
|
+
return true;
|
|
110
|
+
for (const c of when) {
|
|
111
|
+
const r = evaluateCondition(context, c);
|
|
112
|
+
if (r === null)
|
|
113
|
+
return null;
|
|
114
|
+
if (r === false)
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
// ══════════════════════════════════════════
|
|
120
|
+
// resourceBudget → CapabilityToken.constraints
|
|
121
|
+
// ══════════════════════════════════════════
|
|
122
|
+
/** 把 ResourceBudgetSpec 编译为 CapabilityToken.constraints(白名单字段)。 */
|
|
123
|
+
function compileBudgetToConstraints(budget, ruleBudget) {
|
|
124
|
+
// 步骤级覆盖规则级
|
|
125
|
+
const b = budget ?? ruleBudget;
|
|
126
|
+
if (!b)
|
|
127
|
+
return undefined;
|
|
128
|
+
const constraints = {};
|
|
129
|
+
if (typeof b.maxCalls === 'number' && Number.isFinite(b.maxCalls) && b.maxCalls >= 0) {
|
|
130
|
+
constraints.maxCalls = Math.floor(b.maxCalls);
|
|
131
|
+
}
|
|
132
|
+
if (typeof b.maxTokens === 'number' && Number.isFinite(b.maxTokens) && b.maxTokens >= 0) {
|
|
133
|
+
constraints.maxTokens = Math.floor(b.maxTokens);
|
|
134
|
+
}
|
|
135
|
+
if (typeof b.expiryMs === 'number' && Number.isFinite(b.expiryMs) && b.expiryMs >= 0) {
|
|
136
|
+
constraints.expiryMs = Math.floor(b.expiryMs);
|
|
137
|
+
}
|
|
138
|
+
if (b.allowedValues && typeof b.allowedValues === 'object' && !Array.isArray(b.allowedValues)) {
|
|
139
|
+
const allowed = {};
|
|
140
|
+
for (const [k, arr] of Object.entries(b.allowedValues)) {
|
|
141
|
+
if (Array.isArray(arr) && arr.every((v) => typeof v === 'string' && v.length <= exports.MAX_CONDITION_STRING_VALUE_LEN)) {
|
|
142
|
+
if (arr.length <= exports.MAX_CONDITION_LIST_LEN)
|
|
143
|
+
allowed[k] = arr;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (Object.keys(allowed).length > 0)
|
|
147
|
+
constraints.allowedValues = allowed;
|
|
148
|
+
}
|
|
149
|
+
return Object.keys(constraints).length > 0 ? constraints : undefined;
|
|
150
|
+
}
|
|
151
|
+
// ══════════════════════════════════════════
|
|
152
|
+
// 依赖调度(DAG 拓扑 + named outputs)
|
|
153
|
+
// ══════════════════════════════════════════
|
|
154
|
+
/** 拓扑排序步骤;环路/未知依赖返回 null(fail-closed)。 */
|
|
155
|
+
function topologicalOrder(steps) {
|
|
156
|
+
const byId = new Map();
|
|
157
|
+
for (const s of steps) {
|
|
158
|
+
if (!s || typeof s.id !== 'string' || s.id.length === 0)
|
|
159
|
+
return null;
|
|
160
|
+
if (byId.has(s.id))
|
|
161
|
+
return null;
|
|
162
|
+
byId.set(s.id, s);
|
|
163
|
+
}
|
|
164
|
+
const indegree = new Map();
|
|
165
|
+
const children = new Map();
|
|
166
|
+
for (const s of steps) {
|
|
167
|
+
indegree.set(s.id, 0);
|
|
168
|
+
}
|
|
169
|
+
for (const s of steps) {
|
|
170
|
+
const deps = s.dependsOn ?? [];
|
|
171
|
+
for (const d of deps) {
|
|
172
|
+
if (!byId.has(d))
|
|
173
|
+
return null; // 未知依赖
|
|
174
|
+
indegree.set(s.id, (indegree.get(s.id) ?? 0) + 1);
|
|
175
|
+
const list = children.get(d) ?? [];
|
|
176
|
+
list.push(s.id);
|
|
177
|
+
children.set(d, list);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const queue = [];
|
|
181
|
+
for (const [id, deg] of indegree) {
|
|
182
|
+
if (deg === 0)
|
|
183
|
+
queue.push(id);
|
|
184
|
+
}
|
|
185
|
+
queue.sort();
|
|
186
|
+
const order = [];
|
|
187
|
+
while (queue.length > 0) {
|
|
188
|
+
const id = queue.shift();
|
|
189
|
+
order.push(byId.get(id));
|
|
190
|
+
for (const child of children.get(id) ?? []) {
|
|
191
|
+
const nd = (indegree.get(child) ?? 1) - 1;
|
|
192
|
+
indegree.set(child, nd);
|
|
193
|
+
if (nd === 0)
|
|
194
|
+
queue.push(child);
|
|
195
|
+
}
|
|
196
|
+
queue.sort();
|
|
197
|
+
}
|
|
198
|
+
if (order.length !== steps.length)
|
|
199
|
+
return null; // 环
|
|
200
|
+
return order;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 执行一条 WHEN/THEN 规则。
|
|
204
|
+
*
|
|
205
|
+
* - 按 DAG 拓扑顺序调度
|
|
206
|
+
* - 每步先求值 WHEN(解析失败 → fail-closed skip + guardrail 事件)
|
|
207
|
+
* - 输入从初始 context + 上游 named outputs 显式解析(无隐式注入)
|
|
208
|
+
* - 预算编译为 CapabilityToken.constraints 传给 executor
|
|
209
|
+
* - 任一步失败不中断实验:后续依赖步骤 blocked,独立步骤继续
|
|
210
|
+
*/
|
|
211
|
+
async function runWhenThenRule(rule, context, executeStep, options) {
|
|
212
|
+
const events = [];
|
|
213
|
+
const namedOutputs = { ...(options?.initialOutputs ?? {}) };
|
|
214
|
+
const stepStatus = new Map();
|
|
215
|
+
const stepReason = new Map();
|
|
216
|
+
const stepOutputs = new Map();
|
|
217
|
+
const sink = (ev) => {
|
|
218
|
+
events.push(ev);
|
|
219
|
+
options?.onEvent?.(ev);
|
|
220
|
+
};
|
|
221
|
+
// 1. 拓扑排序(环/未知依赖 → 整规则 fail-closed)
|
|
222
|
+
const order = topologicalOrder(rule.steps ?? []);
|
|
223
|
+
if (!order) {
|
|
224
|
+
sink({
|
|
225
|
+
event_type: 'whenthen.rule.failed',
|
|
226
|
+
rule_id: rule.id,
|
|
227
|
+
reason: 'invalid_graph',
|
|
228
|
+
outcome: 'failed',
|
|
229
|
+
timestamp: Date.now(),
|
|
230
|
+
});
|
|
231
|
+
return { success: false, namedOutputs, steps: [], events };
|
|
232
|
+
}
|
|
233
|
+
// 2. 逐步骤
|
|
234
|
+
for (const step of order) {
|
|
235
|
+
const deps = step.dependsOn ?? [];
|
|
236
|
+
const depBlocked = deps.some((d) => {
|
|
237
|
+
const st = stepStatus.get(d);
|
|
238
|
+
return st === 'failed' || st === 'blocked' || st === 'skipped';
|
|
239
|
+
});
|
|
240
|
+
if (depBlocked) {
|
|
241
|
+
stepStatus.set(step.id, 'blocked');
|
|
242
|
+
stepReason.set(step.id, 'upstream_blocked');
|
|
243
|
+
sink({
|
|
244
|
+
event_type: 'whenthen.step.skipped',
|
|
245
|
+
rule_id: rule.id,
|
|
246
|
+
step_id: step.id,
|
|
247
|
+
reason: 'upstream_blocked',
|
|
248
|
+
outcome: 'skipped',
|
|
249
|
+
timestamp: Date.now(),
|
|
250
|
+
});
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
// 3. WHEN 求值(fail-closed)
|
|
254
|
+
const whenOk = evaluateWhen(context, step.when);
|
|
255
|
+
if (whenOk === null) {
|
|
256
|
+
stepStatus.set(step.id, 'skipped');
|
|
257
|
+
stepReason.set(step.id, 'when_parse_failed');
|
|
258
|
+
sink({
|
|
259
|
+
event_type: 'whenthen.step.skipped',
|
|
260
|
+
rule_id: rule.id,
|
|
261
|
+
step_id: step.id,
|
|
262
|
+
reason: 'when_parse_failed',
|
|
263
|
+
outcome: 'skipped',
|
|
264
|
+
timestamp: Date.now(),
|
|
265
|
+
});
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (whenOk === false) {
|
|
269
|
+
stepStatus.set(step.id, 'skipped');
|
|
270
|
+
stepReason.set(step.id, 'when_not_satisfied');
|
|
271
|
+
sink({
|
|
272
|
+
event_type: 'whenthen.step.skipped',
|
|
273
|
+
rule_id: rule.id,
|
|
274
|
+
step_id: step.id,
|
|
275
|
+
reason: 'when_not_satisfied',
|
|
276
|
+
outcome: 'skipped',
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
// 4. 显式 named inputs 解析(路径 = 初始 context 或 namedOutputs)
|
|
282
|
+
const inputs = {};
|
|
283
|
+
if (step.inputs) {
|
|
284
|
+
for (const [name, path] of Object.entries(step.inputs)) {
|
|
285
|
+
if (typeof path !== 'string' || path.length === 0 || path.length > exports.MAX_CONDITION_FIELD_LEN)
|
|
286
|
+
continue;
|
|
287
|
+
if (path.includes('__proto__') || path.includes('constructor') || path.includes('prototype'))
|
|
288
|
+
continue;
|
|
289
|
+
const v = lookupField(namedOutputs, path) ?? lookupField(context, path);
|
|
290
|
+
if (v !== undefined)
|
|
291
|
+
inputs[name] = v;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// 5. 预算编译
|
|
295
|
+
const constraints = compileBudgetToConstraints(step.resourceBudget, rule.resourceBudget);
|
|
296
|
+
// 6. 执行
|
|
297
|
+
try {
|
|
298
|
+
const result = await executeStep({
|
|
299
|
+
stepId: step.id,
|
|
300
|
+
capabilityId: step.capabilityId,
|
|
301
|
+
inputs,
|
|
302
|
+
constraints,
|
|
303
|
+
});
|
|
304
|
+
if (result.success) {
|
|
305
|
+
stepStatus.set(step.id, 'ok');
|
|
306
|
+
const outs = result.outputs ?? {};
|
|
307
|
+
stepOutputs.set(step.id, outs);
|
|
308
|
+
for (const [k, v] of Object.entries(outs)) {
|
|
309
|
+
namedOutputs[k] = v;
|
|
310
|
+
namedOutputs[`${step.id}.${k}`] = v;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
stepStatus.set(step.id, 'failed');
|
|
315
|
+
stepReason.set(step.id, result.error ?? 'step_failed');
|
|
316
|
+
sink({
|
|
317
|
+
event_type: 'whenthen.step.failed',
|
|
318
|
+
rule_id: rule.id,
|
|
319
|
+
step_id: step.id,
|
|
320
|
+
reason: result.error ?? 'step_failed',
|
|
321
|
+
outcome: 'failed',
|
|
322
|
+
timestamp: Date.now(),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
stepStatus.set(step.id, 'failed');
|
|
328
|
+
stepReason.set(step.id, 'step_exception');
|
|
329
|
+
sink({
|
|
330
|
+
event_type: 'whenthen.step.failed',
|
|
331
|
+
rule_id: rule.id,
|
|
332
|
+
step_id: step.id,
|
|
333
|
+
reason: 'step_exception',
|
|
334
|
+
outcome: 'failed',
|
|
335
|
+
timestamp: Date.now(),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const allOk = order.every((s) => stepStatus.get(s.id) === 'ok' || stepStatus.get(s.id) === 'skipped');
|
|
340
|
+
sink({
|
|
341
|
+
event_type: allOk ? 'whenthen.rule.complete' : 'whenthen.rule.failed',
|
|
342
|
+
rule_id: rule.id,
|
|
343
|
+
reason: allOk ? 'complete' : 'partial_failure',
|
|
344
|
+
outcome: allOk ? 'success' : 'partial',
|
|
345
|
+
timestamp: Date.now(),
|
|
346
|
+
});
|
|
347
|
+
return {
|
|
348
|
+
success: allOk,
|
|
349
|
+
namedOutputs,
|
|
350
|
+
steps: order.map((s) => ({
|
|
351
|
+
id: s.id,
|
|
352
|
+
status: stepStatus.get(s.id) ?? 'blocked',
|
|
353
|
+
reason: stepReason.get(s.id),
|
|
354
|
+
outputs: stepOutputs.get(s.id),
|
|
355
|
+
})),
|
|
356
|
+
events,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ingestFeedbackEvents — CLI JSONL → 主进程 SQLite 单向导入(P0)
|
|
3
|
+
*
|
|
4
|
+
* 方向固定:CLI/MCP 只追加 feedback-events.jsonl;主进程定期/按需 ingest。
|
|
5
|
+
* 不做反向同步(spec §九 R3:同步验证前禁止双端各自算权重)。
|
|
6
|
+
* 幂等:靠 idempotency_key 唯一约束,重复导入不增样本。
|
|
7
|
+
* 隔离:规范化失败或 hypothesisId 无对应假设的事件进 quarantine 返回值,不入库、不丢弃。
|
|
8
|
+
*/
|
|
9
|
+
import { type NormalizeQuarantineReason } from './FeedbackEvents';
|
|
10
|
+
import type { DrizzleFeedbackEventStore } from './DrizzleFeedbackEventStore';
|
|
11
|
+
import type { JsonlFeedbackEventStore } from './JsonlFeedbackEventStore';
|
|
12
|
+
export interface IngestFeedbackResult {
|
|
13
|
+
/** 成功写入权威侧的事件数 */
|
|
14
|
+
ingested: number;
|
|
15
|
+
/** 因 idempotencyKey 已存在而跳过 */
|
|
16
|
+
deduped: number;
|
|
17
|
+
/** 规范化失败或假设不存在 */
|
|
18
|
+
quarantined: Array<{
|
|
19
|
+
reason: NormalizeQuarantineReason | 'unknown_hypothesis';
|
|
20
|
+
raw: unknown;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export interface IngestFeedbackOptions {
|
|
24
|
+
/** 校验 hypothesisId 是否指向真实假设;返回 false 的事件进隔离。 */
|
|
25
|
+
hypothesisExists?: (id: string) => boolean;
|
|
26
|
+
now?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @param jsonlSource CLI 侧 JSONL store(或等价的 list() 数据源)
|
|
30
|
+
* @param target 主进程权威 SQLite store
|
|
31
|
+
*/
|
|
32
|
+
export declare function ingestFeedbackEvents(jsonlSource: Pick<JsonlFeedbackEventStore, 'list'>, target: Pick<DrizzleFeedbackEventStore, 'append' | 'hasKey'>, options?: IngestFeedbackOptions): IngestFeedbackResult;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ingestFeedbackEvents — CLI JSONL → 主进程 SQLite 单向导入(P0)
|
|
4
|
+
*
|
|
5
|
+
* 方向固定:CLI/MCP 只追加 feedback-events.jsonl;主进程定期/按需 ingest。
|
|
6
|
+
* 不做反向同步(spec §九 R3:同步验证前禁止双端各自算权重)。
|
|
7
|
+
* 幂等:靠 idempotency_key 唯一约束,重复导入不增样本。
|
|
8
|
+
* 隔离:规范化失败或 hypothesisId 无对应假设的事件进 quarantine 返回值,不入库、不丢弃。
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.ingestFeedbackEvents = ingestFeedbackEvents;
|
|
12
|
+
const FeedbackEvents_1 = require("./FeedbackEvents");
|
|
13
|
+
/**
|
|
14
|
+
* @param jsonlSource CLI 侧 JSONL store(或等价的 list() 数据源)
|
|
15
|
+
* @param target 主进程权威 SQLite store
|
|
16
|
+
*/
|
|
17
|
+
function ingestFeedbackEvents(jsonlSource, target, options = {}) {
|
|
18
|
+
const now = options.now ?? Date.now();
|
|
19
|
+
const result = { ingested: 0, deduped: 0, quarantined: [] };
|
|
20
|
+
for (const raw of jsonlSource.list()) {
|
|
21
|
+
const normalized = (0, FeedbackEvents_1.normalizeFeedbackEvent)(raw, now);
|
|
22
|
+
if (!normalized.ok) {
|
|
23
|
+
result.quarantined.push({ reason: normalized.reason, raw });
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const event = normalized.event;
|
|
27
|
+
if (options.hypothesisExists && !options.hypothesisExists(event.hypothesisId)) {
|
|
28
|
+
result.quarantined.push({ reason: 'unknown_hypothesis', raw });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (target.hasKey(event.idempotencyKey)) {
|
|
32
|
+
result.deduped++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const appendResult = target.append(event);
|
|
36
|
+
if (appendResult.appended)
|
|
37
|
+
result.ingested++;
|
|
38
|
+
else if (appendResult.deduped)
|
|
39
|
+
result.deduped++;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
* - signal: 强制注入 trend/ provocation/insight 来源,限�?randomness
|
|
6
6
|
*/
|
|
7
7
|
export type Strategy = 'stable' | 'explore' | 'signal';
|
|
8
|
+
/**
|
|
9
|
+
* 规范化概念身份(正向反馈闭环 spec §五.2):稳定字段 `origin` 优先,
|
|
10
|
+
* `name` 仅作展示,缺失时回退 `name:<name>` 以避免与 origin 碰撞。
|
|
11
|
+
* ConceptMixer 与 FeedbackLearning 共用此规则,防止双套身份漂移(R2)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function sourceConceptId(s: {
|
|
14
|
+
name: string;
|
|
15
|
+
origin?: string;
|
|
16
|
+
}): string;
|
|
17
|
+
/** 规范化无序概念对键:(A,B) ≡ (B,A)。 */
|
|
18
|
+
export declare function conceptPairKey(a: string, b: string): string;
|
|
8
19
|
export interface CreativitySource {
|
|
9
20
|
name: string;
|
|
10
21
|
content: string;
|
|
@@ -85,7 +96,7 @@ export interface Hypothesis {
|
|
|
85
96
|
impact: number;
|
|
86
97
|
/** 逻辑一致性评分 0-100(第四维,评审/生成时打分,可缺省视为 50) */
|
|
87
98
|
logic?: number;
|
|
88
|
-
status: 'draft' | 'active' | '
|
|
99
|
+
status: 'draft' | 'active' | 'testing' | 'validated' | 'rejected';
|
|
89
100
|
createdAt: number;
|
|
90
101
|
/** The rotation technique this hypothesis was generated with (idea.generate provenance.technique). */
|
|
91
102
|
technique?: string;
|
|
@@ -112,6 +123,20 @@ export interface Hypothesis {
|
|
|
112
123
|
implementedCommitSha?: string;
|
|
113
124
|
implementedAt?: number;
|
|
114
125
|
implementationNote?: string;
|
|
126
|
+
/** 机制假设:提出的关系边(带 proposedRelations 即机制假设,否则普通创意) */
|
|
127
|
+
proposedRelations?: ProposedRelation[];
|
|
128
|
+
/** 证伪条件:命中任一条件则机制假设可被拒绝 */
|
|
129
|
+
falsificationConditions?: string[];
|
|
130
|
+
/** 触发本假设的 observation/trace id 溯源 */
|
|
131
|
+
sourceObservationIds?: string[];
|
|
132
|
+
}
|
|
133
|
+
/** 机制图的一条候选边:A --[effect]--> B */
|
|
134
|
+
export interface ProposedRelation {
|
|
135
|
+
from: string;
|
|
136
|
+
to: string;
|
|
137
|
+
effect: string;
|
|
138
|
+
/** 推断来源:identity=verified | inferred(A6 confidence 映射) */
|
|
139
|
+
inference?: 'identity' | 'inferred';
|
|
115
140
|
}
|
|
116
141
|
export interface ExperimentPlan {
|
|
117
142
|
hypothesisId: string;
|
package/dist/types.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FERMENT_PHASE_OFFSET_MS = exports.FERMENT_INTERVAL_MS = exports.NORMAL_CYCLE_INTERVAL_MS = exports.DREAM_CYCLE_INTERVAL_MS = exports.CREATIVITY_STORE_VERSION = void 0;
|
|
4
|
+
exports.sourceConceptId = sourceConceptId;
|
|
5
|
+
exports.conceptPairKey = conceptPairKey;
|
|
6
|
+
/**
|
|
7
|
+
* 规范化概念身份(正向反馈闭环 spec §五.2):稳定字段 `origin` 优先,
|
|
8
|
+
* `name` 仅作展示,缺失时回退 `name:<name>` 以避免与 origin 碰撞。
|
|
9
|
+
* ConceptMixer 与 FeedbackLearning 共用此规则,防止双套身份漂移(R2)。
|
|
10
|
+
*/
|
|
11
|
+
function sourceConceptId(s) {
|
|
12
|
+
const origin = s.origin ? s.origin.trim() : '';
|
|
13
|
+
return origin || `name:${s.name}`;
|
|
14
|
+
}
|
|
15
|
+
/** 规范化无序概念对键:(A,B) ≡ (B,A)。 */
|
|
16
|
+
function conceptPairKey(a, b) {
|
|
17
|
+
return [a, b].sort().join('|');
|
|
18
|
+
}
|
|
4
19
|
exports.CREATIVITY_STORE_VERSION = 2;
|
|
5
20
|
exports.DREAM_CYCLE_INTERVAL_MS = 30 * 60 * 1000; // 30min
|
|
6
21
|
exports.NORMAL_CYCLE_INTERVAL_MS = 5 * 60 * 1000; // 5min
|