dsh-math-modeling-agent 0.5.1 → 0.5.2
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/README.md
CHANGED
|
@@ -155,7 +155,7 @@ LEGACY_UNVERIFIED → DERIVED → EXECUTED → VERIFIED → INDEPENDENTLY_VERIFI
|
|
|
155
155
|
|
|
156
156
|
**SOLVED 硬门禁**:范围冻结 + 必选 verification 通过并聚合为 `SATISFIED` 的 obligations + `SUPPORTED` claims + 关键对抗检查通过 + 可复现材料齐全 + 局限已声明;High-Assurance 还需独立审计(审计器只读产物,不依赖求解过程的私有推理)。
|
|
157
157
|
|
|
158
|
-
## 当前版本:v0.5.
|
|
158
|
+
## 当前版本:v0.5.2 Evidence-driven pipeline
|
|
159
159
|
|
|
160
160
|
本版本新增:v3 run/ledger evidence graph;独立的 verification / obligation / claim / run 状态;inputs/raw 冻结快照和 SHA-256 校验;typed verification recipes;真实输出量化后的硬约束复检;九段科学报告、symbols 和 Answer Coverage;结构化失败启发;PROJECT_INITIAL_REVIEW 与 PAPER_FINAL_REVIEW 两种论文质量评审;以及不修改父 run 的 correction lineage。
|
|
161
161
|
|
|
@@ -212,7 +212,7 @@ D3 方向/D4 裁决),`run-state.mjs gate` 在每次状态转移前强制校
|
|
|
212
212
|
安装:
|
|
213
213
|
|
|
214
214
|
```bash
|
|
215
|
-
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.5.
|
|
215
|
+
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.5.2
|
|
216
216
|
dsh --profile web --dump-config # 检查组合层(应看到 dsh-math-modeling-agent-skills 行)
|
|
217
217
|
dsh web # 重启以加载插件
|
|
218
218
|
```
|
package/package.json
CHANGED
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"type": "boolean",
|
|
49
49
|
"description": "Require complete chat presentation and user confirmation before each major phase transition."
|
|
50
50
|
},
|
|
51
|
+
"structuredQuestionsRequired": {
|
|
52
|
+
"type": "boolean",
|
|
53
|
+
"description": "Require each L1 decision to originate from the ask_user_question tool."
|
|
54
|
+
},
|
|
51
55
|
"presentations": {
|
|
52
56
|
"type": "array",
|
|
53
57
|
"description": "Chat-first records: complete text presented, content hash, user confirmation and presentation order.",
|
|
@@ -258,7 +258,7 @@ export function createGraph(taskId) {
|
|
|
258
258
|
return {
|
|
259
259
|
schemaVersion: 3,
|
|
260
260
|
taskId,
|
|
261
|
-
scope: { independentAuditPassed: false, interactions: [], decisionStack: [], robustnessExempt: false, chatFirstRequired: true, presentations: [] },
|
|
261
|
+
scope: { independentAuditPassed: false, interactions: [], decisionStack: [], robustnessExempt: false, chatFirstRequired: true, structuredQuestionsRequired: true, presentations: [] },
|
|
262
262
|
requirements: [],
|
|
263
263
|
assumptions: [],
|
|
264
264
|
claims: [],
|
|
@@ -20,6 +20,9 @@ export const CHAT_PHASE_REQUIREMENTS = Object.freeze({
|
|
|
20
20
|
EVALUATE: 'EVALUATION', CORRECTION_REQUIRED: 'CORRECTION', REVISE: 'CORRECTION',
|
|
21
21
|
SOLVED: 'D4', CONDITIONAL: 'D4', PARTIAL: 'D4', INCONCLUSIVE: 'D4', REFUTED: 'D4',
|
|
22
22
|
})
|
|
23
|
+
export const CHAT_DECISION_REQUIREMENTS = Object.freeze({
|
|
24
|
+
D1: 'D1', 'D-R': 'D-R', D0: 'D0', 'D2-G': 'D2-G', D2: 'D2', "D2'": "D2'", D3: 'D3', D4: 'D4',
|
|
25
|
+
})
|
|
23
26
|
|
|
24
27
|
const NON_FINAL_STATES = ['TRIAGE', 'SCOPE_FROZEN', 'INPUT_PROFILED', 'CLAIMS_REGISTERED', 'CANDIDATES_READY', 'ATTEMPT', 'EXECUTE', 'VERIFY', 'EVALUATE', 'CORRECTION_REQUIRED', 'REVISE', 'RESEARCH', 'FORK']
|
|
25
28
|
const FINAL_STATES = ['SOLVED', 'PARTIAL', 'CONDITIONAL', 'INCONCLUSIVE', 'REFUTED', 'INFEASIBLE', 'UNIDENTIFIABLE', 'BLOCKED', 'CANCELLED']
|
|
@@ -404,7 +407,7 @@ export async function initRun(root, { taskId, mode = 'standard', budget, budgets
|
|
|
404
407
|
}
|
|
405
408
|
const ledger = {
|
|
406
409
|
schemaVersion: SCHEMA_VERSION, taskId,
|
|
407
|
-
scope: { independentAuditPassed: false, interactions: [], decisionStack: [], robustnessExempt: false, chatFirstRequired: chatFirstRequired === true, presentations: [], ...(contract === false ? { contractExempt: true } : {}) },
|
|
410
|
+
scope: { independentAuditPassed: false, interactions: [], decisionStack: [], robustnessExempt: false, chatFirstRequired: chatFirstRequired === true, structuredQuestionsRequired: chatFirstRequired === true, presentations: [], ...(contract === false ? { contractExempt: true } : {}) },
|
|
408
411
|
assumptions: [], claims: [], obligations: [], subproblems: [], candidates: [], issues: [],
|
|
409
412
|
requirements: [], evidence: [], verifications: [], failures: [],
|
|
410
413
|
}
|
|
@@ -506,13 +509,46 @@ export function classifyGate(graph, targetState) {
|
|
|
506
509
|
return { allowed: blockers.length === 0 && (targetState !== 'SOLVED' || conditionals.length === 0), blockers, conditionals, requiredNextState: blockers.length > 0 ? 'REVISE' : (conditionals.length > 0 ? 'CONDITIONAL' : targetState) }
|
|
507
510
|
}
|
|
508
511
|
|
|
509
|
-
|
|
512
|
+
function hasStructuredAnswer(value) {
|
|
513
|
+
if (typeof value === 'string') return value.trim() !== ''
|
|
514
|
+
if (Array.isArray(value)) return value.length > 0
|
|
515
|
+
return isObject(value) && Object.keys(value).length > 0
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function decisionPresentationMatches(presentation, decisionPoint, subproblem) {
|
|
519
|
+
if (!presentationIsValid(presentation, presentation?.stage, presentation?.subproblem)) return false
|
|
520
|
+
if (decisionPoint === 'D2' || decisionPoint === "D2'") return presentation.stage === decisionPoint || presentation.stage === subproblem
|
|
521
|
+
return presentation.stage === CHAT_DECISION_REQUIREMENTS[decisionPoint]
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function structuredDecisionIsValid(scope, record, decisionPoint, subproblem) {
|
|
525
|
+
if (!record || record.decisionPoint !== decisionPoint || record.answerSource !== 'ask_user_question') return false
|
|
526
|
+
for (const field of ['id', 'questionId', 'responseId', 'userAnswer', 'presentationId', 'presentedAt', 'userConfirmedAt']) {
|
|
527
|
+
if (typeof record[field] !== 'string' || record[field].trim() === '') return false
|
|
528
|
+
}
|
|
529
|
+
if (!hasStructuredAnswer(record.structuredAnswer)) return false
|
|
530
|
+
if ((decisionPoint === 'D2' || decisionPoint === "D2'") && (typeof subproblem !== 'string' || record.subproblem !== subproblem)) return false
|
|
531
|
+
const presentation = Array.isArray(scope?.presentations) ? scope.presentations.find(value => value?.id === record.presentationId) : null
|
|
532
|
+
return decisionPresentationMatches(presentation, decisionPoint, record.subproblem)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function hasStructuredDecision(scope, decisionPoint, subproblem) {
|
|
536
|
+
return Array.isArray(scope?.interactions) && scope.interactions.some(record => structuredDecisionIsValid(scope, record, decisionPoint, subproblem))
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function structuredDecisionViolations(scope, target, subproblem) {
|
|
540
|
+
if (scope?.structuredQuestionsRequired !== true) return []
|
|
541
|
+
const required = target === 'SCOPE_FROZEN' ? ['D1', 'D-R', 'D0'] : (target === 'CLAIMS_REGISTERED' ? ['D2-G'] : (target === 'CANDIDATES_READY' ? ['D3'] : (target === 'ATTEMPT' ? ['D2'] : (['SOLVED', 'CONDITIONAL', 'PARTIAL', 'INCONCLUSIVE', 'REFUTED', 'INFEASIBLE', 'UNIDENTIFIABLE'].includes(target) ? ['D4'] : []))))
|
|
542
|
+
return required.filter(point => !hasStructuredDecision(scope, point, point === 'D2' ? subproblem : undefined)).map(point => 'structured ask_user_question decision for ' + point + (subproblem ? ' / ' + subproblem : '') + ' is required before ' + target)
|
|
543
|
+
}
|
|
544
|
+
|
|
510
545
|
function hasDecisionRecord(scope, decisionPoint, { allowAuto = true } = {}) {
|
|
511
546
|
return Array.isArray(scope?.interactions) && scope.interactions.some(entry =>
|
|
512
547
|
entry && (entry.decisionPoint === decisionPoint || (allowAuto && entry.auto === true)))
|
|
513
548
|
}
|
|
514
549
|
/** D4 verdict records carry the attempt number they adjudicate: {decisionPoint:'D4', attempt: 2, ...} */
|
|
515
550
|
function hasD4ForAttempt(scope, attempt) {
|
|
551
|
+
if (scope?.structuredQuestionsRequired === true) return hasStructuredDecision(scope, 'D4') && scope.interactions.some(entry => entry.decisionPoint === 'D4' && entry.attempt === attempt)
|
|
516
552
|
return Array.isArray(scope?.interactions) && scope.interactions.some(entry =>
|
|
517
553
|
entry && (entry.auto === true || (entry.decisionPoint === 'D4' && entry.attempt === attempt)))
|
|
518
554
|
}
|
|
@@ -556,6 +592,7 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
|
|
|
556
592
|
const transitional = to !== undefined
|
|
557
593
|
const presentationTarget = transitional ? to : run.status
|
|
558
594
|
violations.push(...chatPresentationViolations(scope, presentationTarget, subproblem))
|
|
595
|
+
violations.push(...structuredDecisionViolations(scope, presentationTarget, subproblem))
|
|
559
596
|
const earlyStop = ['BLOCKED', 'CANCELLED'].includes(run.status)
|
|
560
597
|
const statusOrder = ['TRIAGE', 'RESEARCH', 'SCOPE_FROZEN', 'INPUT_PROFILED', 'CLAIMS_REGISTERED', 'CANDIDATES_READY', 'ATTEMPT', 'EXECUTE', 'VERIFY', 'EVALUATE', 'CORRECTION_REQUIRED', 'REVISE', 'FORK']
|
|
561
598
|
const reached = (target) => transitional
|
|
@@ -652,7 +689,7 @@ function validateAuthoritativeState(run, ledger, events) {
|
|
|
652
689
|
if (diagnostics.length > 0) throw new Error(`invalid run state: ${diagnostics.join('; ')}`)
|
|
653
690
|
}
|
|
654
691
|
|
|
655
|
-
const PRESENTATION_STAGES = new Set(['D-R', ...new Set(Object.values(CHAT_PHASE_REQUIREMENTS))])
|
|
692
|
+
const PRESENTATION_STAGES = new Set(['D-R', ...new Set(Object.values(CHAT_PHASE_REQUIREMENTS)), ...new Set(Object.values(CHAT_DECISION_REQUIREMENTS))])
|
|
656
693
|
|
|
657
694
|
function validateChatPresentationRecord(record) {
|
|
658
695
|
if (!isObject(record)) throw new TypeError('chat presentation must be an object')
|
|
@@ -685,6 +722,34 @@ export async function recordChatPresentation(root, record, runtime = {}) {
|
|
|
685
722
|
}, runtime)
|
|
686
723
|
}
|
|
687
724
|
|
|
725
|
+
function validateStructuredDecisionRecord(record, ledger) {
|
|
726
|
+
if (!isObject(record)) throw new TypeError('structured decision must be an object')
|
|
727
|
+
const decisionPoint = record.decisionPoint
|
|
728
|
+
if (!Object.hasOwn(CHAT_DECISION_REQUIREMENTS, decisionPoint)) throw new TypeError('decisionPoint is not a structured decision point')
|
|
729
|
+
if (decisionPoint === 'D2' || decisionPoint === "D2'") {
|
|
730
|
+
if (typeof record.subproblem !== 'string' || record.subproblem.trim() === '') throw new TypeError(decisionPoint + ' requires subproblem')
|
|
731
|
+
}
|
|
732
|
+
if (decisionPoint === 'D4' && (!Number.isInteger(record.attempt) || record.attempt < 1)) throw new TypeError('D4 requires a positive attempt')
|
|
733
|
+
if (!structuredDecisionIsValid(ledger.scope, record, decisionPoint, record.subproblem)) throw new TypeError('structured decision must come from ask_user_question and link a valid chat presentation')
|
|
734
|
+
return record
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export async function recordStructuredDecision(root, record, runtime = {}) {
|
|
738
|
+
return withLock(root, MUTATION_LOCK_FILE, 'structured-decision', async () => {
|
|
739
|
+
const p = paths(root)
|
|
740
|
+
const ledger = await readJson(p.ledger)
|
|
741
|
+
if (ledger.schemaVersion !== SCHEMA_VERSION) throw new Error('structured decisions require a v3 ledger')
|
|
742
|
+
validateStructuredDecisionRecord(record, ledger)
|
|
743
|
+
ledger.scope ??= {}
|
|
744
|
+
ledger.scope.interactions ??= []
|
|
745
|
+
if (!Array.isArray(ledger.scope.interactions)) throw new Error('ledger.scope.interactions must be an array')
|
|
746
|
+
if (ledger.scope.interactions.some(value => value?.id === record.id)) throw new Error('duplicate structured decision id: ' + record.id)
|
|
747
|
+
ledger.scope.interactions.push(clone(record))
|
|
748
|
+
await atomicWrite(p.ledger, ledger, runtime)
|
|
749
|
+
return clone(record)
|
|
750
|
+
}, runtime)
|
|
751
|
+
}
|
|
752
|
+
|
|
688
753
|
export async function transitionRun(root, input, runtime = {}) {
|
|
689
754
|
validateTransitionInput(input)
|
|
690
755
|
return withLock(root, MUTATION_LOCK_FILE, 'transition', async () => {
|