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.1 Evidence-driven pipeline
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.1
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-math-modeling-agent",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Evidence-driven mathematical modeling and verification skills for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "files": [
@@ -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
- /** The v3 interaction contract: decision records, layered verdicts, and the robustness gate. */
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 () => {