dsh-math-modeling-agent 0.2.6 → 0.2.7
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
|
@@ -108,7 +108,7 @@ D3 方向/D4 裁决),`run-state.mjs gate` 在每次状态转移前强制校
|
|
|
108
108
|
安装:
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.
|
|
111
|
+
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.7
|
|
112
112
|
dsh --profile web --dump-config # 检查组合层(应看到 dsh-math-modeling-agent-skills 行)
|
|
113
113
|
dsh web # 重启以加载插件
|
|
114
114
|
```
|
package/package.json
CHANGED
|
@@ -68,6 +68,16 @@ After the user answers, problem-brief.md is updated and the delta is shown.
|
|
|
68
68
|
4. The D-R interaction record goes into ledger.scope.interactions; the gate
|
|
69
69
|
refuses CANDIDATES_READY without it.
|
|
70
70
|
|
|
71
|
+
## Honest boundary of the gate
|
|
72
|
+
|
|
73
|
+
The gate is a MECHANISM guarantee, not an anti-fraud guarantee: it proves a
|
|
74
|
+
record exists, never that the record is true — every interaction record is
|
|
75
|
+
written by the agent itself. Falsified records are therefore out of the
|
|
76
|
+
gate's reach; the defense against them is the blind verifier subagent
|
|
77
|
+
(subagent-dispatch.md), which re-derives key claims from data alone, and the
|
|
78
|
+
artifact checks (research/sources.jsonl, attempts/N/report.md) that tie each
|
|
79
|
+
record to real files on disk.
|
|
80
|
+
|
|
71
81
|
## Rules
|
|
72
82
|
|
|
73
83
|
- D0/D1/D-R: once per task. D2/D3: once per subproblem. D2': only when a
|
|
@@ -81,7 +91,13 @@ After the user answers, problem-brief.md is updated and the delta is shown.
|
|
|
81
91
|
ATTEMPT-from-CANDIDATES_READY without D3, terminals-from-VERIFY without D4,
|
|
82
92
|
and REVISE/RESEARCH (from VERIFY) whose reason fails to reference a
|
|
83
93
|
`decisionStack` entry id. The CLI `transition` command enforces the same
|
|
84
|
-
gate
|
|
94
|
+
gate and has NO escape hatch. Every D4 record carries the attempt number it
|
|
95
|
+
adjudicates (`attempt: N`); the terminal gate requires the D4 for the
|
|
96
|
+
CURRENT attempt. ATTEMPT from CANDIDATES_READY requires `--subproblem <id>`.
|
|
97
|
+
Terminal gates additionally require `scope.cleanupPassed: true` and the
|
|
98
|
+
current attempt's report.md on disk. Mechanism-level tests and unattended
|
|
99
|
+
batch runs may declare `scope.contractExempt: true` — production modeling
|
|
100
|
+
must never set it.
|
|
85
101
|
|
|
86
102
|
## L2 display points (MUST show, never ask)
|
|
87
103
|
|
|
@@ -366,7 +366,7 @@ function validateJournal(events, diagnostics) {
|
|
|
366
366
|
})
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
export async function initRun(root, { taskId, mode = 'standard', budget, budgets }, runtime = {}) {
|
|
369
|
+
export async function initRun(root, { taskId, mode = 'standard', budget, budgets, contract }, runtime = {}) {
|
|
370
370
|
assertTaskId(taskId); assertMode(mode)
|
|
371
371
|
const effectiveBudget = { ...MODE_DEFAULTS[mode], ...(budget ?? budgets ?? {}) }
|
|
372
372
|
const budgetDiagnostics = []
|
|
@@ -379,7 +379,8 @@ export async function initRun(root, { taskId, mode = 'standard', budget, budgets
|
|
|
379
379
|
bestCandidateId: null, budget: effectiveBudget, createdAt: timestamp, updatedAt: timestamp,
|
|
380
380
|
}
|
|
381
381
|
const ledger = {
|
|
382
|
-
schemaVersion: SCHEMA_VERSION, taskId,
|
|
382
|
+
schemaVersion: SCHEMA_VERSION, taskId,
|
|
383
|
+
scope: { independentAuditPassed: false, interactions: [], decisionStack: [], robustnessExempt: false, ...(contract === false ? { contractExempt: true } : {}) },
|
|
383
384
|
assumptions: [], claims: [], obligations: [], subproblems: [], candidates: [], issues: [],
|
|
384
385
|
}
|
|
385
386
|
const event = { schemaVersion: SCHEMA_VERSION, sequence: 0, type: 'RUN_INITIALIZED', taskId, timestamp, snapshot: clone(run) }
|
|
@@ -412,9 +413,14 @@ export async function initRun(root, { taskId, mode = 'standard', budget, budgets
|
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
/** The v2 interaction contract: decision records, decision-stack backtracking, and the robustness gate. */
|
|
415
|
-
function hasDecisionRecord(scope, decisionPoint) {
|
|
416
|
+
function hasDecisionRecord(scope, decisionPoint, { allowAuto = true } = {}) {
|
|
416
417
|
return Array.isArray(scope?.interactions) && scope.interactions.some(entry =>
|
|
417
|
-
entry && (entry.decisionPoint === decisionPoint || entry.auto === true))
|
|
418
|
+
entry && (entry.decisionPoint === decisionPoint || (allowAuto && entry.auto === true)))
|
|
419
|
+
}
|
|
420
|
+
/** D4 verdict records carry the attempt number they adjudicate: {decisionPoint:'D4', attempt: 2, ...} */
|
|
421
|
+
function hasD4ForAttempt(scope, attempt) {
|
|
422
|
+
return Array.isArray(scope?.interactions) && scope.interactions.some(entry =>
|
|
423
|
+
entry && (entry.auto === true || (entry.decisionPoint === 'D4' && entry.attempt === attempt)))
|
|
418
424
|
}
|
|
419
425
|
function decisionStackIds(scope) {
|
|
420
426
|
return Array.isArray(scope?.decisionStack)
|
|
@@ -430,6 +436,7 @@ function hasSubproblemD2(scope, subproblem) {
|
|
|
430
436
|
function interactionContractViolations(run, ledger, to, reason, subproblem) {
|
|
431
437
|
if (ledger.schemaVersion !== SCHEMA_VERSION) return []
|
|
432
438
|
const scope = ledger.scope ?? {}
|
|
439
|
+
if (scope.contractExempt === true) return []
|
|
433
440
|
const violations = []
|
|
434
441
|
const transitional = to !== undefined
|
|
435
442
|
const earlyStop = ['BLOCKED', 'CANCELLED'].includes(run.status)
|
|
@@ -439,16 +446,17 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
|
|
|
439
446
|
}
|
|
440
447
|
const afterCandidates = ['CANDIDATES_READY', 'ATTEMPT', 'EXECUTE', 'VERIFY', 'REVISE', 'FORK', ...FINAL_STATES]
|
|
441
448
|
const candidatesGate = transitional ? (to === 'CANDIDATES_READY') : (afterCandidates.includes(run.status) && !earlyStop)
|
|
442
|
-
if (candidatesGate && !hasDecisionRecord(scope, 'D-R')) violations.push('D-R literature-research interaction record missing (pre-modeling literature survey is mandatory)')
|
|
449
|
+
if (candidatesGate && !hasDecisionRecord(scope, 'D-R', { allowAuto: false })) violations.push('D-R literature-research interaction record missing (pre-modeling literature survey is mandatory; auto-authorization is not accepted)')
|
|
443
450
|
const enteringAttemptFromCandidates = transitional ? (to === 'ATTEMPT' && run.status === 'CANDIDATES_READY') : (run.currentAttempt >= 1)
|
|
444
451
|
if (enteringAttemptFromCandidates) {
|
|
452
|
+
if (transitional && !subproblem) violations.push('ATTEMPT from CANDIDATES_READY requires --subproblem <id>')
|
|
445
453
|
if (!hasDecisionRecord(scope, 'D3')) violations.push('D3 direction-selection interaction record missing')
|
|
446
|
-
if (transitional && !hasSubproblemD2(scope, subproblem)) violations.push(`D2 assumption interaction record missing for subproblem ${subproblem} (run the gate with --subproblem <id>)`)
|
|
454
|
+
if (transitional && subproblem && !hasSubproblemD2(scope, subproblem)) violations.push(`D2 assumption interaction record missing for subproblem ${subproblem} (run the gate with --subproblem <id>)`)
|
|
447
455
|
}
|
|
448
456
|
const enteringTerminalFromVerify = transitional
|
|
449
457
|
? (FINAL_STATES.includes(to) && run.status === 'VERIFY')
|
|
450
458
|
: (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
|
|
451
|
-
if (enteringTerminalFromVerify && !
|
|
459
|
+
if (enteringTerminalFromVerify && !hasD4ForAttempt(scope, run.currentAttempt)) violations.push(`D4 verdict interaction record for attempt ${run.currentAttempt} missing`)
|
|
452
460
|
if (transitional && (to === 'REVISE' || (to === 'RESEARCH' && run.status === 'VERIFY'))) {
|
|
453
461
|
const ids = decisionStackIds(scope)
|
|
454
462
|
if (ids.length > 0 && !ids.some(id => typeof reason === 'string' && reason.includes(id))) {
|
|
@@ -458,9 +466,12 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
|
|
|
458
466
|
const terminalAndExempt = transitional
|
|
459
467
|
? (FINAL_STATES.includes(to) && !['BLOCKED', 'CANCELLED'].includes(to))
|
|
460
468
|
: (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
|
|
461
|
-
if (terminalAndExempt
|
|
462
|
-
|
|
463
|
-
|
|
469
|
+
if (terminalAndExempt) {
|
|
470
|
+
if (scope.robustnessExempt !== true) {
|
|
471
|
+
const passed = Array.isArray(ledger.obligations) && ledger.obligations.some(o => o && o.kind === 'robustness' && o.status === 'PASS')
|
|
472
|
+
if (!passed) violations.push('terminal state requires a PASSED robustness-kind obligation (or scope.robustnessExempt: true)')
|
|
473
|
+
}
|
|
474
|
+
if (scope.cleanupPassed !== true) violations.push('terminal state requires scope.cleanupPassed: true (run the cleanup checklist in run-directory.md)')
|
|
464
475
|
}
|
|
465
476
|
return violations
|
|
466
477
|
}
|
|
@@ -550,7 +561,7 @@ async function validateRunUnlocked(root, expectedTaskId) {
|
|
|
550
561
|
if (ledger.schemaVersion !== SCHEMA_VERSION) warnings.push(`legacy run (ledger schema v${ledger.schemaVersion}): interaction contract not enforced`)
|
|
551
562
|
else {
|
|
552
563
|
for (const violation of interactionContractViolations(run, ledger, undefined, undefined)) {
|
|
553
|
-
|
|
564
|
+
diagnostics.push(`interaction contract: ${violation}`)
|
|
554
565
|
}
|
|
555
566
|
if (run.currentAttempt >= 1) {
|
|
556
567
|
const reportPath = join(root, 'attempts', String(run.currentAttempt), 'report.md')
|
|
@@ -574,7 +585,7 @@ export async function validateRun(root, expectedTaskId, runtime = {}) {
|
|
|
574
585
|
/**
|
|
575
586
|
* Check the v2 interaction contract for a PROSPECTIVE transition without
|
|
576
587
|
* mutating anything. The agent workflow must call this before `transition`;
|
|
577
|
-
* the CLI `transition` command enforces it internally
|
|
588
|
+
* the CLI `transition` command enforces it internally and has NO escape hatch.
|
|
578
589
|
*/
|
|
579
590
|
export async function gateTransition(root, { to, reason, subproblem }, runtime = {}) {
|
|
580
591
|
return withLock(root, MUTATION_LOCK_FILE, 'gate', async () => {
|
|
@@ -582,6 +593,23 @@ export async function gateTransition(root, { to, reason, subproblem }, runtime =
|
|
|
582
593
|
const run = await readJson(p.run)
|
|
583
594
|
const ledger = await readJson(p.ledger)
|
|
584
595
|
const violations = interactionContractViolations(run, ledger, to, reason, subproblem)
|
|
596
|
+
const exempt = ledger.scope?.contractExempt === true
|
|
597
|
+
if (to === 'CANDIDATES_READY' && !exempt) {
|
|
598
|
+
const sourcesPath = join(root, 'research', 'sources.jsonl')
|
|
599
|
+
try {
|
|
600
|
+
if (!(await readFile(sourcesPath, 'utf8')).trim()) violations.push('research/sources.jsonl is empty (pre-modeling literature survey artifacts are mandatory)')
|
|
601
|
+
} catch {
|
|
602
|
+
violations.push('research/sources.jsonl missing (pre-modeling literature survey artifacts are mandatory)')
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (FINAL_STATES.includes(to) && !['BLOCKED', 'CANCELLED'].includes(to) && !exempt) {
|
|
606
|
+
const reportPath = join(root, 'attempts', String(run.currentAttempt), 'report.md')
|
|
607
|
+
try {
|
|
608
|
+
await readFile(reportPath, 'utf8')
|
|
609
|
+
} catch {
|
|
610
|
+
violations.push(`attempts/${run.currentAttempt}/report.md missing (write the runlog report before a terminal transition)`)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
585
613
|
return { allowed: violations.length === 0, violations, status: run.status }
|
|
586
614
|
}, runtime)
|
|
587
615
|
}
|
|
@@ -632,12 +660,10 @@ async function cli(argv) {
|
|
|
632
660
|
return result
|
|
633
661
|
}
|
|
634
662
|
if (command === 'transition') {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
return { error: `interaction contract violated: ${gate.violations.join('; ')}`, gate }
|
|
640
|
-
}
|
|
663
|
+
const gate = await gateTransition(root, { to: options.to, reason: options.reason, subproblem: options.subproblem })
|
|
664
|
+
if (!gate.allowed) {
|
|
665
|
+
process.exitCode = 1
|
|
666
|
+
return { error: `interaction contract violated: ${gate.violations.join('; ')}`, gate }
|
|
641
667
|
}
|
|
642
668
|
return transitionRun(root, { to: options.to, reason: options.reason, evidenceIds: options.evidence ? options.evidence.split(',').filter(Boolean) : [], issueIds: options.issues ? options.issues.split(',').filter(Boolean) : [], patch: options.candidate ? { bestCandidateId: options.candidate } : undefined })
|
|
643
669
|
}
|