@gobing-ai/spur 0.3.72 → 0.3.73
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/.claude-plugin/marketplace.json +1 -1
- package/config/config.example.yaml +33 -23
- package/config/corpus-baseline.json +168 -0
- package/config/pipeline-budgets.json +7 -3
- package/config/plugin-scripts.json +4 -0
- package/config/proportional-route-table.ts +155 -0
- package/config/rules/structure/protected-files.yaml +3 -0
- package/config/task-pipeline-proportional-migration-plan.md +79 -0
- package/config/workflow-composition-baseline.json +124 -85
- package/config/workflows/docs-pipeline.yaml +11 -1
- package/config/workflows/feature-dev.yaml +27 -20
- package/config/workflows/task-lifecycle.yaml +27 -15
- package/config/workflows/task-pipeline.yaml +72 -11
- package/config/workflows/wrapup-pipeline.yaml +100 -42
- package/package.json +9 -9
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +2 -4
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +2 -4
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +185 -0
- package/plugins/sp/scripts/task-evidence-precheck.ts +1 -1
- package/plugins/sp/scripts/verify-answer-lint.ts +4 -0
- package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +23 -0
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +20 -0
- package/schemas/state-machine-workflow.schema.json +3 -1
- package/schemas/transition-flow-workflow.schema.json +3 -1
- package/spur.js +12471 -10279
|
@@ -35,6 +35,29 @@ bootstrap:
|
|
|
35
35
|
url: .spur/spur.db # ${DATABASE_URL} interpolation supported
|
|
36
36
|
scheduler:
|
|
37
37
|
enabled: false # CLI is run-once; no scheduler
|
|
38
|
+
# Periodic executions live here and nowhere else (task 0750). A tick
|
|
39
|
+
# enqueues `scheduler.custom`, which the server's job worker runs through
|
|
40
|
+
# /bin/sh -c in the project root. Use the repo-local CLI, never a global
|
|
41
|
+
# `spur` that may be a stale bundle (AGENTS.md history-validation rule).
|
|
42
|
+
jobs:
|
|
43
|
+
# Incremental refresh, waking hours only (07:00-23:50; the cron hour range
|
|
44
|
+
# keeps 00:00-06:59 quiet). `history daily` = import + analyze; analyze is
|
|
45
|
+
# what refreshes the Board History rollups (token usage included) - import
|
|
46
|
+
# alone never does. Checkpoint resume + the rollup-freshness short-circuit
|
|
47
|
+
# keep a no-op tick cheap (I81).
|
|
48
|
+
- name: history-refresh
|
|
49
|
+
cron: "*/10 7-23 * * *"
|
|
50
|
+
command: spur --no-logo history daily
|
|
51
|
+
|
|
52
|
+
# 05:00 daily: full-corpus import (checkpoint resume self-heals gaps),
|
|
53
|
+
# then the twelve-section history-anatomy diagnostic report headless. On
|
|
54
|
+
# a cache hit the workflow skips the token-expensive model stages and
|
|
55
|
+
# republishes provenance (ADR-079), so a ready report costs no tokens.
|
|
56
|
+
- name: history-daily-report
|
|
57
|
+
cron: "3 5 * * *"
|
|
58
|
+
command: >-
|
|
59
|
+
spur --no-logo history import --source all &&
|
|
60
|
+
spur --no-logo workflow run config/workflows/history-anatomy.yaml --quiet
|
|
38
61
|
|
|
39
62
|
# ── Spur app section (validated by spurConfigSchema in
|
|
40
63
|
# @gobing-ai/spur-config) ──
|
|
@@ -85,13 +108,14 @@ agent:
|
|
|
85
108
|
# ~/.codex/config.toml, ~/.claude.json). The ids below are working examples,
|
|
86
109
|
# not vendor defaults — re-pin for your accounts.
|
|
87
110
|
#
|
|
88
|
-
# Pairing philosophy (operator guidance, 2026-08): a `native` pair — a
|
|
89
|
-
# model served by that vendor's own CLI (gpt-5.6-* via codex,
|
|
90
|
-
# claude, grok-4.6 via grok, gemini-3.x via agy) —
|
|
91
|
-
# same model routed through a third-party CLI,
|
|
92
|
-
# `portable` models (glm-5.x,
|
|
93
|
-
# carried by omp or pi at
|
|
94
|
-
#
|
|
111
|
+
# Pairing philosophy (operator guidance, 2026-08): a `native` pair — a
|
|
112
|
+
# vendor's model served by that vendor's own CLI (gpt-5.6-* via codex,
|
|
113
|
+
# claude-*-5 via claude, grok-4.6 via grok, gemini-3.x via agy) —
|
|
114
|
+
# consistently outperforms the same model routed through a third-party CLI,
|
|
115
|
+
# and belongs at the capable rungs. `portable` models (glm-5.x,
|
|
116
|
+
# deepseek-v4-*, …) are provider-agnostic and best carried by omp or pi at
|
|
117
|
+
# the cheap/standard rungs. The ladder below mixes both classes on purpose;
|
|
118
|
+
# measure pairings with the history plane before promoting
|
|
95
119
|
# a portable model into a capable rung.
|
|
96
120
|
# Live tiers: cheap | standard | capable-1 | capable-2 | capable-3
|
|
97
121
|
# (1=low quality within capable, 3=high). DECLARE tier — inference never
|
|
@@ -155,7 +179,8 @@ agent:
|
|
|
155
179
|
# - role: reviewer
|
|
156
180
|
# - role: coder
|
|
157
181
|
# executor: omp
|
|
158
|
-
# purpose: "Pinned executor, role recorded alongside
|
|
182
|
+
# purpose: "Pinned executor, role recorded alongside
|
|
183
|
+
# (pin beats policy)"
|
|
159
184
|
|
|
160
185
|
rules:
|
|
161
186
|
paths:
|
|
@@ -180,18 +205,3 @@ tasks:
|
|
|
180
205
|
|
|
181
206
|
features:
|
|
182
207
|
dir: docs/features
|
|
183
|
-
|
|
184
|
-
# ── History (task 0549 — completion-triggered refresh) ──
|
|
185
|
-
# Opt-in: when work completes (task → done, pipeline run reaching
|
|
186
|
-
# terminal status), enqueue ONE coalesced `history.refresh` job on the
|
|
187
|
-
# embedded job queue (never inline). Bursts inside debounce_ms join the
|
|
188
|
-
# pending job; the covered window spans all completions. The job reuses
|
|
189
|
-
# `spur history daily` semantics (import-all fan-out with per-source
|
|
190
|
-
# isolation + analyze + artifact write) and is consumed by `spur serve`'s
|
|
191
|
-
# job worker. Debounce default 600000 ms (10 min) follows
|
|
192
|
-
# docs/tasks4/0548-import-cost-measurement.md (steady-state all-fanout
|
|
193
|
-
# import ≈ 20.6 s; 10 min comfortably bounds burst coalescing).
|
|
194
|
-
# history:
|
|
195
|
-
# refresh:
|
|
196
|
-
# on_completion: false # set true to enable; disable with no code edits
|
|
197
|
-
# debounce_ms: 600000 # coalescing window; floor 1000 ms
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"note": "Machine-generated snapshot of observed corpus findings. Regenerate: bun run scripts/commands/regen-corpus-baseline.ts. Wave 2026-08-28 (ADR-092): sweep scope is the ACTIVE task folder only; docs/tasks{,2,3} are read-only history. Single-sided gate (ADR-090): vanished entries retire via regeneration, not gate failure. Retires the ADR-083 per-entry reason/since annotations. Dated decision note (0688 friction review, 2026-08-27): new task citations prefer path:symbol over path:line (docs/04_DESIGN.md §4.2, task 0694).",
|
|
3
|
+
"waiver": {
|
|
4
|
+
"owner": "spur-dev-maintainers",
|
|
5
|
+
"review_date": "2026-12-01",
|
|
6
|
+
"removal_criterion": "The corpus snapshot retires when its 299 entries have all been repaired (or accepted as real signal) and the active folder's sweep produces zero baseline-reconciled findings. Until then, the snapshot is a temporary waiver per ADR-093: a 4-month review window from the 0754 commit date, with objective removal tied to a zero-finding sweep. The single-sided snapshot mechanism (ADR-090) ensures a shrinking snapshot is a visible signal, not silent debt."
|
|
7
|
+
},
|
|
8
|
+
"generated_at": "2026-09-03",
|
|
3
9
|
"entries": [
|
|
4
10
|
{
|
|
5
11
|
"kind": "feature",
|
|
@@ -151,6 +157,24 @@
|
|
|
151
157
|
"code": "L4.dogfood-missing",
|
|
152
158
|
"severity": "warning"
|
|
153
159
|
},
|
|
160
|
+
{
|
|
161
|
+
"kind": "feature",
|
|
162
|
+
"id": "D9",
|
|
163
|
+
"code": "L4.evidence-not-recoverable",
|
|
164
|
+
"severity": "warning"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"kind": "feature",
|
|
168
|
+
"id": "D9",
|
|
169
|
+
"code": "L4.scenario-unverified",
|
|
170
|
+
"severity": "warning"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"kind": "feature",
|
|
174
|
+
"id": "D9",
|
|
175
|
+
"code": "L4.uncovered-feature-scenario",
|
|
176
|
+
"severity": "warning"
|
|
177
|
+
},
|
|
154
178
|
{
|
|
155
179
|
"kind": "feature",
|
|
156
180
|
"id": "E",
|
|
@@ -205,6 +229,24 @@
|
|
|
205
229
|
"code": "L4.orphan-scenarios",
|
|
206
230
|
"severity": "warning"
|
|
207
231
|
},
|
|
232
|
+
{
|
|
233
|
+
"kind": "feature",
|
|
234
|
+
"id": "E81",
|
|
235
|
+
"code": "L4.evidence-not-recoverable",
|
|
236
|
+
"severity": "warning"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"kind": "feature",
|
|
240
|
+
"id": "E81",
|
|
241
|
+
"code": "L4.scenario-unverified",
|
|
242
|
+
"severity": "warning"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"kind": "feature",
|
|
246
|
+
"id": "E91",
|
|
247
|
+
"code": "L4.scenario-unverified",
|
|
248
|
+
"severity": "warning"
|
|
249
|
+
},
|
|
208
250
|
{
|
|
209
251
|
"kind": "feature",
|
|
210
252
|
"id": "F",
|
|
@@ -913,6 +955,12 @@
|
|
|
913
955
|
"code": "L4.scenario-unverified",
|
|
914
956
|
"severity": "warning"
|
|
915
957
|
},
|
|
958
|
+
{
|
|
959
|
+
"kind": "feature",
|
|
960
|
+
"id": "L",
|
|
961
|
+
"code": "L4.orphan-scenarios",
|
|
962
|
+
"severity": "warning"
|
|
963
|
+
},
|
|
916
964
|
{
|
|
917
965
|
"kind": "feature",
|
|
918
966
|
"id": "M1",
|
|
@@ -1513,6 +1561,18 @@
|
|
|
1513
1561
|
"code": "L4.stale-line-anchor",
|
|
1514
1562
|
"severity": "warning"
|
|
1515
1563
|
},
|
|
1564
|
+
{
|
|
1565
|
+
"kind": "task",
|
|
1566
|
+
"id": "0641",
|
|
1567
|
+
"code": "L4.stale-line-anchor",
|
|
1568
|
+
"severity": "warning"
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
"kind": "task",
|
|
1572
|
+
"id": "0646",
|
|
1573
|
+
"code": "L4.stale-line-anchor",
|
|
1574
|
+
"severity": "warning"
|
|
1575
|
+
},
|
|
1516
1576
|
{
|
|
1517
1577
|
"kind": "task",
|
|
1518
1578
|
"id": "0657",
|
|
@@ -1579,6 +1639,12 @@
|
|
|
1579
1639
|
"code": "L4.uncovered-task-scenario",
|
|
1580
1640
|
"severity": "warning"
|
|
1581
1641
|
},
|
|
1642
|
+
{
|
|
1643
|
+
"kind": "task",
|
|
1644
|
+
"id": "0689",
|
|
1645
|
+
"code": "L4.stale-line-anchor",
|
|
1646
|
+
"severity": "warning"
|
|
1647
|
+
},
|
|
1582
1648
|
{
|
|
1583
1649
|
"kind": "task",
|
|
1584
1650
|
"id": "0690",
|
|
@@ -1632,6 +1698,108 @@
|
|
|
1632
1698
|
"id": "0698",
|
|
1633
1699
|
"code": "L4.stale-line-anchor",
|
|
1634
1700
|
"severity": "warning"
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
"kind": "task",
|
|
1704
|
+
"id": "0719",
|
|
1705
|
+
"code": "L4.stale-line-anchor",
|
|
1706
|
+
"severity": "warning"
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
"kind": "task",
|
|
1710
|
+
"id": "0741",
|
|
1711
|
+
"code": "L3.unchecked-checklist",
|
|
1712
|
+
"severity": "warning"
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
"kind": "task",
|
|
1716
|
+
"id": "0745",
|
|
1717
|
+
"code": "L3.unchecked-checklist",
|
|
1718
|
+
"severity": "warning"
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
"kind": "task",
|
|
1722
|
+
"id": "0751",
|
|
1723
|
+
"code": "L3.unchecked-checklist",
|
|
1724
|
+
"severity": "warning"
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
"kind": "task",
|
|
1728
|
+
"id": "0751",
|
|
1729
|
+
"code": "L4.stale-line-anchor",
|
|
1730
|
+
"severity": "warning"
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
"kind": "task",
|
|
1734
|
+
"id": "0753",
|
|
1735
|
+
"code": "L3.unchecked-checklist",
|
|
1736
|
+
"severity": "warning"
|
|
1737
|
+
},
|
|
1738
|
+
{
|
|
1739
|
+
"kind": "task",
|
|
1740
|
+
"id": "0753",
|
|
1741
|
+
"code": "L4.stale-line-anchor",
|
|
1742
|
+
"severity": "warning"
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
"kind": "task",
|
|
1746
|
+
"id": "0755",
|
|
1747
|
+
"code": "L3.unchecked-checklist",
|
|
1748
|
+
"severity": "warning"
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
"kind": "task",
|
|
1752
|
+
"id": "0755",
|
|
1753
|
+
"code": "L4.stale-line-anchor",
|
|
1754
|
+
"severity": "warning"
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
"kind": "task",
|
|
1758
|
+
"id": "0756",
|
|
1759
|
+
"code": "L3.unchecked-checklist",
|
|
1760
|
+
"severity": "warning"
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
"kind": "task",
|
|
1764
|
+
"id": "0757",
|
|
1765
|
+
"code": "L3.unchecked-checklist",
|
|
1766
|
+
"severity": "warning"
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
"kind": "task",
|
|
1770
|
+
"id": "0758",
|
|
1771
|
+
"code": "L4.prerequisite-not-done",
|
|
1772
|
+
"severity": "warning"
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
"kind": "task",
|
|
1776
|
+
"id": "0759",
|
|
1777
|
+
"code": "L4.prerequisite-not-done",
|
|
1778
|
+
"severity": "warning"
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
"kind": "task",
|
|
1782
|
+
"id": "0761",
|
|
1783
|
+
"code": "L3.ac-empty",
|
|
1784
|
+
"severity": "error"
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
"kind": "task",
|
|
1788
|
+
"id": "0761",
|
|
1789
|
+
"code": "L3.requirements-checkbox",
|
|
1790
|
+
"severity": "warning"
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
"kind": "task",
|
|
1794
|
+
"id": "0762",
|
|
1795
|
+
"code": "L3.ac-empty",
|
|
1796
|
+
"severity": "error"
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
"kind": "task",
|
|
1800
|
+
"id": "0762",
|
|
1801
|
+
"code": "L3.requirements-checkbox",
|
|
1802
|
+
"severity": "warning"
|
|
1635
1803
|
}
|
|
1636
1804
|
]
|
|
1637
1805
|
}
|
|
@@ -25,11 +25,15 @@
|
|
|
25
25
|
"decision": null
|
|
26
26
|
},
|
|
27
27
|
"docs-pipeline": {
|
|
28
|
-
"modelQueries":
|
|
28
|
+
"modelQueries": 2,
|
|
29
29
|
"wallClockMs": null,
|
|
30
30
|
"tokenCostUsd": null,
|
|
31
|
-
"source": "No fixture (0607 option a) and only dry/short runs recorded in history (max 1s, n=4) — wall-clock budget unenforced until a real docs-pipeline run exists. Query budget 1 is
|
|
32
|
-
"decision":
|
|
31
|
+
"source": "No fixture (0607 option a) and only dry/short runs recorded in history (max 1s, n=4) — wall-clock budget unenforced until a real docs-pipeline run exists. Query budget 2 matches the live SSOT list ['draft','verify'] in config/workflow-composition-baseline.json (added by task 0704; 0607 R3 recorded 1, which is stale against the SSOT). 0754 R6: FIX, not raise — the value was wrong against the SSOT, the workflow's declared query count did not change.",
|
|
32
|
+
"decision": {
|
|
33
|
+
"date": "2026-09-03",
|
|
34
|
+
"wbs": "0754",
|
|
35
|
+
"note": "align docs-pipeline.modelQueries to the live baseline SSOT ['draft','verify']; not a measured-cost raise — the workflow's declared query count was already 2 (added by 0704); the budget was the stale side, not the workflow. Per D8 Decision 11."
|
|
36
|
+
}
|
|
33
37
|
},
|
|
34
38
|
"pr-review": {
|
|
35
39
|
"modelQueries": 0,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proportional route table — D8 S3 pilot scaffold (task 0758).
|
|
3
|
+
*
|
|
4
|
+
* The route table is a closed, mutually exhaustive predicate map. Every input
|
|
5
|
+
* resolves to exactly one route. Missing, unknown, or conflicting evidence
|
|
6
|
+
* always selects the safety path (never a fast path). The safety floor
|
|
7
|
+
* (proof-bracket guards, budget-unverifiable fail-closed dispatch,
|
|
8
|
+
* reviewer/executor independence, run-id confinement) holds on the fast
|
|
9
|
+
* path exactly as on the safety path — no route trades the safety floor
|
|
10
|
+
* for speed.
|
|
11
|
+
*
|
|
12
|
+
* See: docs/plans/2026-09-02-d8-proportional-workflow-upgrade-strategy.md §4
|
|
13
|
+
* (route table contract) and §7 (pilot exit bar).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type RouteId = 'safety' | 'fast' | 'skipped';
|
|
17
|
+
|
|
18
|
+
export interface RoutePredicate {
|
|
19
|
+
/** Stable id for the route this predicate selects. */
|
|
20
|
+
readonly route: RouteId;
|
|
21
|
+
/** Human label for review. */
|
|
22
|
+
readonly label: string;
|
|
23
|
+
/** Stable predicate id for evidence-bound replay. */
|
|
24
|
+
readonly id: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RouteEvaluation {
|
|
28
|
+
readonly route: RouteId;
|
|
29
|
+
readonly predicateId: string;
|
|
30
|
+
readonly reason: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The closed route table for proportional routing.
|
|
35
|
+
*/
|
|
36
|
+
export const ROUTE_TABLE: readonly RoutePredicate[] = Object.freeze([
|
|
37
|
+
{
|
|
38
|
+
id: 'safety-default',
|
|
39
|
+
route: 'safety',
|
|
40
|
+
label: 'default / unknown / conflicting evidence',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'fast-complete',
|
|
44
|
+
route: 'fast',
|
|
45
|
+
label: 'complete and consistent evidence',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'skipped-empty',
|
|
49
|
+
route: 'skipped',
|
|
50
|
+
label: 'empty task list / no work required',
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Evaluate route for wrapup-pipeline over (tasks, mode).
|
|
56
|
+
* Mutually exhaustive predicates matching strategy §4 and prototype 0732 §2.
|
|
57
|
+
*/
|
|
58
|
+
export function evaluateWrapupRoute(input: { tasks?: unknown[] | string; mode?: string }): RouteEvaluation {
|
|
59
|
+
let taskCount = 0;
|
|
60
|
+
if (Array.isArray(input.tasks)) {
|
|
61
|
+
taskCount = input.tasks.length;
|
|
62
|
+
} else if (typeof input.tasks === 'string') {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(input.tasks);
|
|
65
|
+
if (Array.isArray(parsed)) taskCount = parsed.length;
|
|
66
|
+
} catch {
|
|
67
|
+
taskCount = 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (taskCount === 0) {
|
|
72
|
+
return {
|
|
73
|
+
route: 'skipped',
|
|
74
|
+
predicateId: 'skipped-empty',
|
|
75
|
+
reason: 'skipped:empty task list',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const mode = input.mode;
|
|
80
|
+
if (mode === 'fast') {
|
|
81
|
+
return {
|
|
82
|
+
route: 'fast',
|
|
83
|
+
predicateId: 'fast-complete',
|
|
84
|
+
reason: 'fast:evidence complete+consistent',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (!mode) {
|
|
88
|
+
return {
|
|
89
|
+
route: 'safety',
|
|
90
|
+
predicateId: 'safety-default',
|
|
91
|
+
reason: 'safety:missing evidence (mode empty)',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (mode === 'unknown') {
|
|
95
|
+
return {
|
|
96
|
+
route: 'safety',
|
|
97
|
+
predicateId: 'safety-default',
|
|
98
|
+
reason: 'safety:unknown evidence quality',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (mode === 'conflict') {
|
|
102
|
+
return {
|
|
103
|
+
route: 'safety',
|
|
104
|
+
predicateId: 'safety-default',
|
|
105
|
+
reason: 'safety:conflicting evidence',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
route: 'safety',
|
|
110
|
+
predicateId: 'safety-default',
|
|
111
|
+
reason: `safety:unrecognized evidence (mode=${mode})`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generic evaluateRoute resolving to safety or fast based on input.
|
|
117
|
+
*/
|
|
118
|
+
export function evaluateRoute(input: RouteInput): RouteEvaluation {
|
|
119
|
+
if (input.costCoverage >= 0.8 && input.proofBinding === 'current' && input.reviewerIndependent) {
|
|
120
|
+
return {
|
|
121
|
+
route: 'fast',
|
|
122
|
+
predicateId: 'fast-complete',
|
|
123
|
+
reason: 'fast:evidence complete+consistent',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
route: 'safety',
|
|
128
|
+
predicateId: 'safety-default',
|
|
129
|
+
reason: 'safety:default / unknown / conflicting evidence',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Input shape for route evaluation. Frozen interface so the follow-up
|
|
135
|
+
* pilot session extends it without changing call sites.
|
|
136
|
+
*/
|
|
137
|
+
export interface RouteInput {
|
|
138
|
+
readonly runId: string;
|
|
139
|
+
readonly definitionDigest: string;
|
|
140
|
+
readonly evidenceRefs: readonly string[];
|
|
141
|
+
readonly costCoverage: number;
|
|
142
|
+
readonly proofBinding: 'current' | 'stale' | 'missing';
|
|
143
|
+
readonly reviewerIndependent: boolean;
|
|
144
|
+
readonly runIdConfined: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Safety floor invariant. Every route — safety or fast — must hold these
|
|
149
|
+
* conditions. The fast path cannot trade any of them for speed. This
|
|
150
|
+
* function is the gate the pilot's run-bound evidence writer must
|
|
151
|
+
* enforce before recording a non-safety route.
|
|
152
|
+
*/
|
|
153
|
+
export function safetyFloorHolds(input: RouteInput): boolean {
|
|
154
|
+
return input.proofBinding === 'current' && input.reviewerIndependent === true && input.runIdConfined === true;
|
|
155
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# task-pipeline proportional migration plan (task 0759 WIP)
|
|
2
|
+
|
|
3
|
+
**Status:** WIP — gated on 0758 pilot completion. The route table contract proven
|
|
4
|
+
on wrapup-pipeline and task-lifecycle is reused unchanged. No code change to
|
|
5
|
+
task-pipeline.yaml until the pilot clears the bar.
|
|
6
|
+
|
|
7
|
+
## Prerequisites (all satisfied at the time of this commit)
|
|
8
|
+
|
|
9
|
+
- **0751** — proof primitives fail-closed: ✅ done in this branch.
|
|
10
|
+
- **0752** — resume-to-definition binding: ✅ done in this branch.
|
|
11
|
+
- **0753** — workflow-seam repairs (command.gate timeout, run-id validation, nested
|
|
12
|
+
composition, dry-probe escalation): ✅ done in this branch.
|
|
13
|
+
- **0757** — re-measure gate recorded Option A continues (wrapup-pipeline 40 real
|
|
14
|
+
terminal runs, task-lifecycle 27, both pilots clear ≥5): ✅ done in this branch.
|
|
15
|
+
- **0758** — route table proven on the two pilots with ≥5 real terminal runs each
|
|
16
|
+
and ≥80% run-scoped cost row coverage: **WIP** in this branch. The scaffold is in
|
|
17
|
+
`config/proportional-route-table.ts`; the pilot runs accumulate in a follow-up
|
|
18
|
+
session.
|
|
19
|
+
|
|
20
|
+
## Migration contract (frozen; no changes expected from the pilot)
|
|
21
|
+
|
|
22
|
+
1. **Same closed route table.** `config/proportional-route-table.ts` is the data
|
|
23
|
+
structure. task-pipeline adopts it unchanged. The fast-path predicates are
|
|
24
|
+
per-pilot: wrapup-pipeline and task-lifecycle get theirs from the 0758 pilot;
|
|
25
|
+
task-pipeline gets its predicates after the pilot proves the shape.
|
|
26
|
+
|
|
27
|
+
2. **Same safety floor.** `safetyFloorHolds()` is the gate. proofBinding
|
|
28
|
+
`current`, reviewerIndependent `true`, runIdConfined `true` — all three must
|
|
29
|
+
hold on every route.
|
|
30
|
+
|
|
31
|
+
3. **Same evidence-writing interface.** The run-bound evidence writer (R4/R5) is
|
|
32
|
+
implemented once in the 0758 follow-up; task-pipeline uses the same writer.
|
|
33
|
+
No per-workflow duplication.
|
|
34
|
+
|
|
35
|
+
4. **Same revertability property (R7).** task-pipeline gets a per-workflow
|
|
36
|
+
`proportional_routing: enabled|disabled` switch (default: disabled until the
|
|
37
|
+
migration lands). Rollback is one config change, not an engine change.
|
|
38
|
+
|
|
39
|
+
## What task-pipeline.yaml changes when the migration lands
|
|
40
|
+
|
|
41
|
+
A single block at the top of `config/workflows/task-pipeline.yaml`:
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
proportional_routing:
|
|
45
|
+
enabled: true
|
|
46
|
+
route_table: ../proportional-route-table.ts#ROUTE_TABLE
|
|
47
|
+
safety_floor: ../proportional-route-table.ts#safetyFloorHolds
|
|
48
|
+
evidence_writer: ../proportional-route-evidence-writer.ts # lands in 0758 follow-up
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
No change to states, transitions, actions, or guards in this commit. The
|
|
52
|
+
migration lands only after the 0758 pilot clears and the operator approves
|
|
53
|
+
the change (plan §7 S5 operator consent gate).
|
|
54
|
+
|
|
55
|
+
## Acceptance criteria mapping (task 0759)
|
|
56
|
+
|
|
57
|
+
- R1 — closed route table: ✅ frozen in `config/proportional-route-table.ts`; task-pipeline
|
|
58
|
+
adopts unchanged.
|
|
59
|
+
- R2 — safety floor: ✅ frozen in `safetyFloorHolds()`; task-pipeline enforces the
|
|
60
|
+
same invariant.
|
|
61
|
+
- R3 — no regression: PENDING — requires real terminal runs after migration
|
|
62
|
+
with pre/post comparison.
|
|
63
|
+
- R4 — real engine, no fixture: ✅ invariant is a design property; no fixture
|
|
64
|
+
substitution in this branch.
|
|
65
|
+
- R5 — verified-outcome binding: ✅ already shipped via 0751 R4 (the
|
|
66
|
+
`proofBinding: current` guard); the migration reuses it.
|
|
67
|
+
- R6 — measured bounds: ✅ none adjusted in this branch; per-bound justification
|
|
68
|
+
required at migration time.
|
|
69
|
+
- R7 — revertable: ✅ `proportional_routing.enabled: false` is the rollback.
|
|
70
|
+
|
|
71
|
+
## Open items (all blocked on 0758 pilot)
|
|
72
|
+
|
|
73
|
+
- Per-pilot fast-path predicate values (R1).
|
|
74
|
+
- Run-bound evidence writer implementation (R4, R5).
|
|
75
|
+
- Pre/post terminal-run comparison (R3).
|
|
76
|
+
- Operator consent for the task-pipeline.yaml change (plan §7 S5).
|
|
77
|
+
|
|
78
|
+
Status moves from WIP to done when the 0758 pilot clears the bar and the
|
|
79
|
+
follow-up session implements the per-pilot predicates and the evidence writer.
|