yaaw-se 0.3.9 → 0.3.11-dev.36385821931.1

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.
@@ -28,8 +28,8 @@
28
28
  },
29
29
  {
30
30
  "path": "payload.json",
31
- "sha256": "a0cc2f108e5025cb574d243489bdd7e0696c709261d8d17adbb0653f0f6ec142",
32
- "bytes": 56
31
+ "sha256": "e6777f48978f169c8951edfb209cfd1978a370d281c0ea0ba5f5ec1142f17433",
32
+ "bytes": 75
33
33
  },
34
34
  {
35
35
  "path": "skills/yaaw-create-spec/SKILL.md",
@@ -53,8 +53,8 @@
53
53
  },
54
54
  {
55
55
  "path": "skills/yaaw-orchestrator/SKILL.md",
56
- "sha256": "a87e912c81b5cdab3bc76aaa922f9f3092e53e689e31e1458d71dfc9f03da57e",
57
- "bytes": 666
56
+ "sha256": "981e912d418cd833e8281034f739e206894134a496766caacb97471b9577ec1c",
57
+ "bytes": 864
58
58
  },
59
59
  {
60
60
  "path": "skills/yaaw-planner/SKILL.md",
@@ -133,8 +133,8 @@
133
133
  },
134
134
  {
135
135
  "path": "yaaw-core/system/core/lifecycle.md",
136
- "sha256": "0efde7d7fa09b949ca21cc5819f66979bb31a7992013ae8b600382592d09d98c",
137
- "bytes": 946
136
+ "sha256": "c1d089198c96b3f1f2c5c8a3b4c98ba5f9a429e1d99d15dde05f4b9807fb54f8",
137
+ "bytes": 1224
138
138
  },
139
139
  {
140
140
  "path": "yaaw-core/system/core/recovery.md",
@@ -148,8 +148,8 @@
148
148
  },
149
149
  {
150
150
  "path": "yaaw-core/system/core/state-model.md",
151
- "sha256": "f50950a49937e1311ac23e9a2b24861db59afeffc7bd0e663dc21efef9130f63",
152
- "bytes": 948
151
+ "sha256": "203c3b5a12d65cdcec3a47bc25b40607e0b1733c99a4e43449f6042a6574172e",
152
+ "bytes": 1224
153
153
  },
154
154
  {
155
155
  "path": "yaaw-core/system/core/transitions.md",
@@ -198,8 +198,8 @@
198
198
  },
199
199
  {
200
200
  "path": "yaaw-core/system/registries/handoff-policy.json",
201
- "sha256": "17238dea4fafe4c098d9528d55ba760b38e9276277cbb0439e304bbb5e744126",
202
- "bytes": 4859
201
+ "sha256": "a679a166bfb30349eec16602d5e0df366df4f6176246b578d7967e25d123efdb",
202
+ "bytes": 4921
203
203
  },
204
204
  {
205
205
  "path": "yaaw-core/system/registries/paths.json",
@@ -208,8 +208,8 @@
208
208
  },
209
209
  {
210
210
  "path": "yaaw-core/system/registries/reconciliation-policy.json",
211
- "sha256": "4523cbf644b352e1029682fbf990e44b3ba00afd61738f532a465a035f712129",
212
- "bytes": 697
211
+ "sha256": "795745109cc165d806cfe3d43d5836077f962b910faf3e8ba3a8396f74e9b7d2",
212
+ "bytes": 745
213
213
  },
214
214
  {
215
215
  "path": "yaaw-core/system/registries/role-io.json",
@@ -218,8 +218,8 @@
218
218
  },
219
219
  {
220
220
  "path": "yaaw-core/system/registries/routing-policy.json",
221
- "sha256": "05d869fc4b0b62c12d45717d2c51fb10a8d0eb1fa85c2fc7c3f4cc6abee99810",
222
- "bytes": 1951
221
+ "sha256": "8a8bb2dd65bbd8338081fdd275042a35fac1a70b52d0eacb62d24f15bae59499",
222
+ "bytes": 2008
223
223
  },
224
224
  {
225
225
  "path": "yaaw-core/system/registries/skills.json",
@@ -486,15 +486,20 @@
486
486
  "sha256": "27e76946abf1abde306a561621f5a7833a164f3af85db83f5db22b6809450ba6",
487
487
  "bytes": 4812
488
488
  },
489
+ {
490
+ "path": "yaaw-core/system/tools/frontmatter.mjs",
491
+ "sha256": "2c90097563a91c52e6f9c7748dac583e67967fd4a9748e393bf61d753594277e",
492
+ "bytes": 5474
493
+ },
489
494
  {
490
495
  "path": "yaaw-core/system/tools/orchestration-engine.mjs",
491
- "sha256": "5058465a85ef3f59d9da1159f7dd871f73930be95e7eaeeb11a1fa6cf4edd3c9",
492
- "bytes": 18583
496
+ "sha256": "b14d2f6d0c25246aab9a2c84adaad4a20b41d1857755725b145cde48cda4123b",
497
+ "bytes": 20364
493
498
  },
494
499
  {
495
500
  "path": "yaaw-core/system/tools/orchestration-runtime.mjs",
496
- "sha256": "25797671d0c84d2e08499854c7e6327d67e7628a786255e6c294d291c638659e",
497
- "bytes": 14566
501
+ "sha256": "e25923b994867d5dea2c6e9a7391ecd66371a042ee95a1ccc1553f963464efc7",
502
+ "bytes": 13540
498
503
  },
499
504
  {
500
505
  "path": "yaaw-core/system/tools/repository-identity.mjs",
@@ -523,8 +528,8 @@
523
528
  },
524
529
  {
525
530
  "path": "yaaw-core/system/workflows/orchestration/determine-next-action.md",
526
- "sha256": "8093c6e857edaee8410d66b2854b4034d7b94a9dedf27ef4adf5e01451a182c4",
527
- "bytes": 1299
531
+ "sha256": "69ca31d6323b0b1d10a8bd4b17423b8f4c81df095f495c29c0231ff609fa9eb0",
532
+ "bytes": 1407
528
533
  },
529
534
  {
530
535
  "path": "yaaw-core/system/workflows/orchestration/dispatch.md",
@@ -543,13 +548,13 @@
543
548
  },
544
549
  {
545
550
  "path": "yaaw-core/system/workflows/orchestration/recover-interruption.md",
546
- "sha256": "b563109190e7026e3ce4073ed37771a8265f97fd35c201ace514789d4aefa309",
547
- "bytes": 942
551
+ "sha256": "a8a692aa1461d7230ecb4b7d05c0d258b45a3956930c34dd9d38dadf7490660d",
552
+ "bytes": 2147
548
553
  },
549
554
  {
550
555
  "path": "yaaw-core/system/workflows/orchestration/route.md",
551
- "sha256": "e86f8e377bcbcea4d215841d01f88e6dc4d98a0c020297e678f3f557577db1aa",
552
- "bytes": 1168
556
+ "sha256": "5965bf77dc93b8ad89a177fbf06e3465b14ba41ba9f47f3e440a7884c9a777a5",
557
+ "bytes": 1353
553
558
  },
554
559
  {
555
560
  "path": "yaaw-core/system/workflows/planning/create-spec.md",
@@ -558,8 +563,8 @@
558
563
  },
559
564
  {
560
565
  "path": "yaaw-core/system/workflows/planning/create-tickets.md",
561
- "sha256": "298691aecdb40563f93bd62de3a0433913202be70c7de8e4e4ac2af5ae840ac0",
562
- "bytes": 1547
566
+ "sha256": "b868389870177c13cccb76635ea6f057b53164861b1a7090de532c1b8ac7aa0d",
567
+ "bytes": 4028
563
568
  },
564
569
  {
565
570
  "path": "yaaw-core/system/workflows/planning/decision-frontier.md",
@@ -598,8 +603,8 @@
598
603
  },
599
604
  {
600
605
  "path": "yaaw-core/system/workflows/planning/route.md",
601
- "sha256": "2dd2492dc4a6a3b2d83dd80ee74d6134b01f4ea0396d91c9b7ce12dbd393832d",
602
- "bytes": 1476
606
+ "sha256": "fa3bf9bc6c47868f7e2d85235669e73065bdbcdb4a7812304ea37f6989641449",
607
+ "bytes": 1710
603
608
  },
604
609
  {
605
610
  "path": "yaaw-core/system/workflows/planning/write-understanding.md",
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "schema": "yaaw.payload/v1",
3
- "version": "0.3.9"
3
+ "version": "0.3.11-dev.36385821931.1"
4
4
  }
@@ -11,4 +11,4 @@ INTENT: `CONTINUE`
11
11
  Resolve the YAAW workspace root, then invoke:
12
12
  `node .yaaw-core/system/tools/orchestration-runtime.mjs --workspace <WORKSPACE_ROOT> --invoke-skill yaaw-orchestrator`
13
13
 
14
- Follow `orchestration.route` until `CONTINUE_UNTIL_STOP` is satisfied or a human-input, BLOCKED, or framework stop occurs. Do not execute orchestrator semantics directly from this wrapper; every semantic execution requires the exact runtime handoff.
14
+ Follow `orchestration.route` until `CONTINUE_UNTIL_STOP` is satisfied or a human-input, BLOCKED, or framework stop occurs. A `FRAMEWORK_STOP` is terminal for the current invocation: report it once and do not re-run the unchanged runtime unless the installation version, manifest, or managed framework bytes have changed. Do not execute orchestrator semantics directly from this wrapper; every semantic execution requires the exact runtime handoff.
@@ -7,6 +7,8 @@ product missing/unready -> PRD
7
7
  product ready, planning unresolved -> Planner
8
8
  planning ready, spec missing -> create/adopt spec
9
9
  spec accepted, tickets missing -> create/register tickets
10
+ registered DRAFT + dependencies PASS -> Planner re-evaluates admission
11
+ Planner persists current DRAFT -> READY -> Orchestrator adopts TICKET_ADMISSION
10
12
  READY -> implementation_start fact -> IN_PROGRESS
11
13
  IN_PROGRESS -> PASS verification fact -> REVIEW_REQUIRED
12
14
  REVIEW_REQUIRED -> immutable Reviewer result -> PASS | REPAIR_REQUIRED | REPLAN_REQUIRED | BLOCKED
@@ -15,7 +17,7 @@ PASS + scope_status OPEN/UNKNOWN -> Planner
15
17
  all current tickets PASS/CANCELLED + scope_status COMPLETE -> COMPLETE
16
18
  ```
17
19
 
18
- One reconciliation is applied per observation cycle; recovery never jumps directly from `READY` to `REVIEW_REQUIRED`.
20
+ One reconciliation is applied per observation cycle; recovery never jumps directly from `READY` to `REVIEW_REQUIRED`. Recovery is interruption-only and must not be used as the fallback for a current `DRAFT` ticket or missing planning admission.
19
21
 
20
22
  ## Fresh-context invariant
21
23
  Conversation may disappear at any point. Durable artifacts and repository evidence must still identify the correct next boundary.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Canonical project state is `.yaaw-core/project/state.json` using `yaaw.project-state/v2`.
4
4
 
5
- State is a routing/adoption ledger, not semantic authority. Ticket frontmatter is admission/history metadata and never overrides the reconciled ticket lifecycle value in state.
5
+ State is a routing/adoption ledger, not semantic authority. Ticket frontmatter is admission/history metadata and never directly overrides the reconciled ticket lifecycle value in state. A Planner-owned `DRAFT -> READY` artifact change is instead a candidate durable admission fact: Orchestrator validates current sources, planning readiness, dependency completion, repository identity, and the legal transition before adopting it as `TICKET_ADMISSION`.
6
6
 
7
7
  ## State writer
8
8
  Orchestrator is the only physical writer of `state.json`. Semantic roles create durable facts: Planner creates engineering/spec/ticket facts, Implementer creates start/verification evidence, Reviewer creates immutable review results. Orchestrator validates and adopts those facts through legal transitions.
@@ -23,9 +23,9 @@
23
23
  },
24
24
  "planning.create-tickets": {
25
25
  "desired_intent": "CREATE_TICKETS", "active_artifact": "spec",
26
- "reads": ["product","engineering","engineering_research","spec","project_rule","repository","state"], "writes": ["ticket"],
26
+ "reads": ["product","engineering","engineering_research","spec","ticket","project_rule","repository","state"], "writes": ["ticket"],
27
27
  "required_expertise": [], "ticket_expertise": false,
28
- "expected_output": "Dependency-aware executable tickets for the accepted specification or a typed prerequisite/stop result.",
28
+ "expected_output": "Dependency-aware tickets for the accepted specification, including re-evaluation of current DRAFT admission candidates, or a typed prerequisite/stop result.",
29
29
  "result_vocabulary": ["SUCCESS","PRECONDITION_UNSATISFIED","BLOCKED"]
30
30
  },
31
31
  "planning.replan": {
@@ -6,12 +6,13 @@
6
6
  {"id": "PLANNING_LEDGER_SYNC", "priority": 3},
7
7
  {"id": "ACTIVE_SPEC_UNADOPTED", "priority": 4},
8
8
  {"id": "TICKET_UNREGISTERED", "priority": 5},
9
- {"id": "CONTRACT_SOURCE_STALE", "priority": 6},
10
- {"id": "IMPLEMENTATION_STARTED", "priority": 7},
11
- {"id": "IMPLEMENTATION_VERIFIED", "priority": 8},
12
- {"id": "REVIEW_RESULT_UNAPPLIED", "priority": 9},
13
- {"id": "ACCEPTANCE_STALE", "priority": 10},
14
- {"id": "PROJECT_COMPLETION_SYNC", "priority": 11}
9
+ {"id": "TICKET_ADMISSION", "priority": 6},
10
+ {"id": "CONTRACT_SOURCE_STALE", "priority": 7},
11
+ {"id": "IMPLEMENTATION_STARTED", "priority": 8},
12
+ {"id": "IMPLEMENTATION_VERIFIED", "priority": 9},
13
+ {"id": "REVIEW_RESULT_UNAPPLIED", "priority": 10},
14
+ {"id": "ACCEPTANCE_STALE", "priority": 11},
15
+ {"id": "PROJECT_COMPLETION_SYNC", "priority": 12}
15
16
  ],
16
17
  "one_reconciliation_per_cycle": true
17
18
  }
@@ -4,6 +4,7 @@
4
4
  "planning_unready_workflow": "planning.route",
5
5
  "missing_spec_workflow": "planning.create-spec",
6
6
  "missing_tickets_workflow": "planning.create-tickets",
7
+ "draft_admission_workflow": "planning.create-tickets",
7
8
  "next_frontier_workflow": "planning.route",
8
9
  "complete_terminal": "COMPLETE",
9
10
  "ticket_state_precedence": [
@@ -0,0 +1,173 @@
1
+ function indentOf(line) {
2
+ const match = String(line).match(/^ */);
3
+ return match ? match[0].length : 0;
4
+ }
5
+
6
+ function unquote(raw) {
7
+ if (raw.length >= 2 && raw.startsWith("\"") && raw.endsWith("\"")) {
8
+ try { return JSON.parse(raw); } catch { return raw.slice(1, -1); }
9
+ }
10
+ if (raw.length >= 2 && raw.startsWith("'") && raw.endsWith("'")) {
11
+ return raw.slice(1, -1).replace(/''/g, "'");
12
+ }
13
+ return raw;
14
+ }
15
+
16
+ function splitInline(raw) {
17
+ const out = [];
18
+ let start = 0, quote = null, depth = 0;
19
+ for (let i = 0; i < raw.length; i += 1) {
20
+ const ch = raw[i];
21
+ if (quote) {
22
+ if (ch === quote && raw[i - 1] !== "\\") quote = null;
23
+ continue;
24
+ }
25
+ if (ch === "\"" || ch === "'") { quote = ch; continue; }
26
+ if (ch === "[" || ch === "{") depth += 1;
27
+ else if (ch === "]" || ch === "}") depth -= 1;
28
+ else if (ch === "," && depth === 0) {
29
+ out.push(raw.slice(start, i));
30
+ start = i + 1;
31
+ }
32
+ }
33
+ out.push(raw.slice(start));
34
+ return out;
35
+ }
36
+
37
+ export function parseScalar(value) {
38
+ const raw = String(value ?? "").trim();
39
+ if (raw === "") return "";
40
+ if (raw === "null" || raw === "~") return null;
41
+ if (raw === "true") return true;
42
+ if (raw === "false") return false;
43
+ if (/^-?\d+$/.test(raw)) return Number(raw);
44
+ if (raw.startsWith("[") && raw.endsWith("]")) {
45
+ try { return JSON.parse(raw); } catch {}
46
+ const body = raw.slice(1, -1).trim();
47
+ if (!body) return [];
48
+ return splitInline(body).map(item => parseScalar(item));
49
+ }
50
+ if (raw.startsWith("{") && raw.endsWith("}")) {
51
+ try { return JSON.parse(raw); } catch {}
52
+ }
53
+ return unquote(raw);
54
+ }
55
+
56
+ function nextContent(lines, index, end) {
57
+ for (let i = index; i < end; i += 1) {
58
+ const trimmed = lines[i].trim();
59
+ if (trimmed && !trimmed.startsWith("#")) return i;
60
+ }
61
+ return end;
62
+ }
63
+
64
+ function keyValue(text) {
65
+ const index = text.indexOf(":");
66
+ if (index <= 0) return null;
67
+ return [text.slice(0, index).trim(), text.slice(index + 1).trim()];
68
+ }
69
+
70
+ function parseMapping(lines, start, end, indent) {
71
+ const out = {};
72
+ let i = start;
73
+ while (i < end) {
74
+ i = nextContent(lines, i, end);
75
+ if (i >= end) break;
76
+ const raw = lines[i], current = indentOf(raw), trimmed = raw.trim();
77
+ if (current < indent) break;
78
+ if (current > indent) throw new Error(`unexpected indentation at frontmatter line ${i + 1}`);
79
+ if (trimmed.startsWith("-")) break;
80
+ const pair = keyValue(trimmed);
81
+ if (!pair) throw new Error(`invalid frontmatter mapping at line ${i + 1}`);
82
+ const [key, rest] = pair;
83
+ if (rest !== "") {
84
+ out[key] = parseScalar(rest);
85
+ i += 1;
86
+ continue;
87
+ }
88
+ const next = nextContent(lines, i + 1, end);
89
+ if (next >= end || indentOf(lines[next]) <= indent) {
90
+ out[key] = {};
91
+ i += 1;
92
+ continue;
93
+ }
94
+ const [child, after] = parseNode(lines, next, end, indentOf(lines[next]));
95
+ out[key] = child;
96
+ i = after;
97
+ }
98
+ return [out, i];
99
+ }
100
+
101
+ function parseSequence(lines, start, end, indent) {
102
+ const out = [];
103
+ let i = start;
104
+ while (i < end) {
105
+ i = nextContent(lines, i, end);
106
+ if (i >= end) break;
107
+ const raw = lines[i], current = indentOf(raw), trimmed = raw.trim();
108
+ if (current < indent) break;
109
+ if (current > indent) throw new Error(`unexpected indentation at frontmatter line ${i + 1}`);
110
+ if (!trimmed.startsWith("-")) break;
111
+ const body = trimmed.slice(1).trim();
112
+ if (body === "") {
113
+ const next = nextContent(lines, i + 1, end);
114
+ if (next >= end || indentOf(lines[next]) <= indent) {
115
+ out.push(null);
116
+ i += 1;
117
+ continue;
118
+ }
119
+ const [child, after] = parseNode(lines, next, end, indentOf(lines[next]));
120
+ out.push(child);
121
+ i = after;
122
+ continue;
123
+ }
124
+ const pair = keyValue(body);
125
+ if (pair) {
126
+ const item = {};
127
+ const [key, rest] = pair;
128
+ item[key] = rest === "" ? {} : parseScalar(rest);
129
+ const next = nextContent(lines, i + 1, end);
130
+ if (next < end && indentOf(lines[next]) > indent) {
131
+ const childIndent = indentOf(lines[next]);
132
+ if (rest === "") {
133
+ const [child, after] = parseNode(lines, next, end, childIndent);
134
+ item[key] = child;
135
+ i = after;
136
+ } else {
137
+ const [extra, after] = parseMapping(lines, next, end, childIndent);
138
+ Object.assign(item, extra);
139
+ i = after;
140
+ }
141
+ } else {
142
+ i += 1;
143
+ }
144
+ out.push(item);
145
+ continue;
146
+ }
147
+ out.push(parseScalar(body));
148
+ i += 1;
149
+ }
150
+ return [out, i];
151
+ }
152
+
153
+ function parseNode(lines, start, end, indent) {
154
+ const index = nextContent(lines, start, end);
155
+ if (index >= end) return [{}, end];
156
+ return lines[index].trim().startsWith("-")
157
+ ? parseSequence(lines, index, end, indent)
158
+ : parseMapping(lines, index, end, indent);
159
+ }
160
+
161
+ export function parseFrontmatter(text) {
162
+ const lines = String(text).split(/\r?\n/);
163
+ if (lines[0]?.trim() !== "---") throw new Error("missing opening frontmatter");
164
+ const relativeEnd = lines.slice(1).findIndex(line => line.trim() === "---");
165
+ if (relativeEnd < 0) throw new Error("missing closing frontmatter");
166
+ const end = relativeEnd + 1;
167
+ const start = nextContent(lines, 1, end);
168
+ if (start >= end) return {};
169
+ const [value, after] = parseMapping(lines, start, end, indentOf(lines[start]));
170
+ const trailing = nextContent(lines, after, end);
171
+ if (trailing < end) throw new Error(`unsupported frontmatter syntax at line ${trailing + 1}`);
172
+ return value;
173
+ }
@@ -31,7 +31,7 @@ function transition(id,subject,from,to,reason,evidence=[]){return{id,kind:"STATE
31
31
  function currentTicketIds(state,a){const s=activeSpec(state,a);return s?Object.keys(state?.tickets??{}).filter(id=>ticketSourceStatus(a?.tickets?.[id]?.meta,state,s)==="CURRENT").sort():[];}
32
32
 
33
33
  export function selectReconciliation({state,artifacts:a,evidence=[],reviews=[],repository,transitions,reconciliationPolicy,routingPolicy}){
34
- const order=["PROJECT_STATE_SCHEMA_MIGRATION_REQUIRED","PRODUCT_LEDGER_SYNC","PLANNING_LEDGER_SYNC","ACTIVE_SPEC_UNADOPTED","TICKET_UNREGISTERED","CONTRACT_SOURCE_STALE","IMPLEMENTATION_STARTED","IMPLEMENTATION_VERIFIED","REVIEW_RESULT_UNAPPLIED","ACCEPTANCE_STALE","PROJECT_COMPLETION_SYNC"];
34
+ const order=["PROJECT_STATE_SCHEMA_MIGRATION_REQUIRED","PRODUCT_LEDGER_SYNC","PLANNING_LEDGER_SYNC","ACTIVE_SPEC_UNADOPTED","TICKET_UNREGISTERED","TICKET_ADMISSION","CONTRACT_SOURCE_STALE","IMPLEMENTATION_STARTED","IMPLEMENTATION_VERIFIED","REVIEW_RESULT_UNAPPLIED","ACCEPTANCE_STALE","PROJECT_COMPLETION_SYNC"];
35
35
  const declared=(reconciliationPolicy?.priority??[]).map(x=>typeof x==="string"?x:x.id);
36
36
  if(declared.length&&JSON.stringify(declared)!==JSON.stringify(order))return{id:"RECONCILIATION_POLICY_DRIFT",kind:"BLOCKED",reason:"reconciliation priority differs from engine contract"};
37
37
  if(!state)return{id:"PROJECT_STATE_MISSING",kind:"BLOCKED",reason:"project state missing"};
@@ -65,6 +65,17 @@ export function selectReconciliation({state,artifacts:a,evidence=[],reviews=[],r
65
65
  if(ticketSourceStatus(x.meta,state,s)!=="CURRENT")continue;
66
66
  return{id:"TICKET_UNREGISTERED",kind:"STATE",subject:id,from:null,to:x.meta.status,reason:"current ticket artifact not registered",workflow:"orchestration.reconcile-state",evidence:[x.path],patch:{ticket:id,status:x.meta.status}};
67
67
  }
68
+ for(const id of Object.keys(state.tickets??{}).sort()){
69
+ const st=state.tickets[id],m=a?.tickets?.[id]?.meta;
70
+ if(st!=="DRAFT"||String(m?.status??"")!=="READY")continue;
71
+ if(ticketSourceStatus(m,state,s)!=="CURRENT")continue;
72
+ if(normalizeStatus(state?.planning?.status)!=="ready"||state?.planning?.readiness!=="PASS")continue;
73
+ if(!deps(id,a,state)||repository?.status!=="READY")continue;
74
+ const contract=(transitions?.legal??[]).find(x=>x.from==="DRAFT"&&x.to==="READY");
75
+ if(contract?.owner!=="planner"||contract?.workflow!=="planning.create-tickets")
76
+ return{id:"ILLEGAL_TICKET_ADMISSION_CONTRACT",kind:"BLOCKED",reason:"DRAFT -> READY admission must be owned by Planner planning.create-tickets"};
77
+ return transition("TICKET_ADMISSION",id,"DRAFT","READY","PLANNER_ADMISSION_CURRENT",[a.tickets[id].path,s.path]);
78
+ }
68
79
  for(const id of Object.keys(state.tickets??{}).sort()){
69
80
  const st=state.tickets[id];if(!EXECUTABLE.has(st))continue;
70
81
  const cause=ticketSourceStatus(a?.tickets?.[id]?.meta,state,s);
@@ -143,9 +154,13 @@ function baseRoute(state,a,evidence,reviews,repository,p){
143
154
  }
144
155
  const ip=ids.find(x=>tickets[x]==="IN_PROGRESS");if(ip){const m=a.tickets[ip].meta;return start(evidence,ip,Number(m.revision),Number(s.revision))?{kind:"DISPATCH_READY",workflow:"implementation.verify-ticket",ticket:ip}:{kind:"ROOT_ACTION",workflow:"orchestration.recover-interruption",ticket:ip,reason:"IMPLEMENTATION_START_EVIDENCE_MISSING"};}
145
156
  const ready=ids.find(x=>tickets[x]==="READY"&&deps(x,a,state));if(ready)return{kind:"DISPATCH_READY",workflow:"implementation.implement-ticket",ticket:ready};
157
+ const draft=ids.find(x=>tickets[x]==="DRAFT"&&deps(x,a,state));if(draft)return{kind:"DISPATCH_READY",workflow:p.draft_admission_workflow??"planning.create-tickets",ticket:draft,reason:"TICKET_ADMISSION_REQUIRED"};
146
158
  const vals=ids.map(x=>tickets[x]);if(vals.some(x=>x==="BLOCKED"))return{kind:"BLOCKED",terminal:"BLOCKED",reason:"TICKET_BLOCKED"};
147
159
  if(vals.length&&vals.every(x=>x==="PASS"||x==="CANCELLED"))return state.planning.scope_status==="COMPLETE"?{kind:"TERMINAL",terminal:p.complete_terminal}:{kind:"DISPATCH_READY",workflow:p.next_frontier_workflow};
148
- return{kind:"ROOT_ACTION",workflow:"orchestration.recover-interruption",reason:"NO_SAFE_SEMANTIC_ROUTE"};
160
+ if(vals.some(x=>x==="DRAFT"))return{kind:"DISPATCH_READY",workflow:p.next_frontier_workflow,reason:"DRAFT_DEPENDENCIES_UNSATISFIED"};
161
+ const unsupported=vals.find(x=>!["READY","IN_PROGRESS","REVIEW_REQUIRED","REPAIR_REQUIRED","REPLAN_REQUIRED","BLOCKED","PASS","CANCELLED","DRAFT"].includes(x));
162
+ if(unsupported)return{kind:"BLOCKED",terminal:"BLOCKED",reason:"UNSUPPORTED_TICKET_STATE:"+unsupported};
163
+ return{kind:"DISPATCH_READY",workflow:p.next_frontier_workflow,reason:"PLANNING_ROUTE_REQUIRED"};
149
164
  }
150
165
  function legalNow(w,state,a){
151
166
  const s=activeSpec(state,a),t=state?.tickets??{},productReady=normalizeStatus(state?.product?.status)==="ready";
@@ -153,7 +168,7 @@ function legalNow(w,state,a){
153
168
  if(["prd.revise","prd.refine"].includes(w))return Boolean(a?.product?.meta);
154
169
  if(["planning.route","planning.readiness-review"].includes(w))return productReady;
155
170
  if(w==="planning.create-spec")return productReady&&normalizeStatus(state.planning.status)==="ready"&&state.planning.readiness==="PASS"&&!s;
156
- if(w==="planning.create-tickets")return Boolean(s)&&currentTicketIds(state,a).length===0;
171
+ if(w==="planning.create-tickets"){const ids=currentTicketIds(state,a);return Boolean(s)&&(ids.length===0||ids.some(id=>t[id]==="DRAFT"&&deps(id,a,state)));}
157
172
  if(w==="implementation.implement-ticket")return Object.values(t).includes("READY");
158
173
  if(w==="implementation.repair-ticket")return Object.values(t).includes("REPAIR_REQUIRED");
159
174
  if(w==="review.review-ticket")return Object.values(t).includes("REVIEW_REQUIRED");
@@ -162,7 +177,7 @@ function legalNow(w,state,a){
162
177
  export function intentComplete(i,state,a){
163
178
  if(!i)return false;const target=String(i.target_artifact??"").match(/TASK-[0-9]+/)?.[0]??null,o=i.desired_outcome,advanced=Number(state?.last_transition?.sequence??0)>Number(i.created_from_transition_sequence??0);
164
179
  if(o==="CREATE_SPEC")return currentAcceptedSpecs(state,a).length===1;
165
- if(o==="CREATE_TICKETS")return currentTicketIds(state,a).length>0;
180
+ if(o==="CREATE_TICKETS"){const ids=currentTicketIds(state,a);return ids.length>0&&!ids.some(id=>state.tickets?.[id]==="DRAFT"&&deps(id,a,state));}
166
181
  if(o==="IMPLEMENT"||o==="REPAIR")return target?state.tickets?.[target]==="REVIEW_REQUIRED":advanced&&state.last_transition?.to==="REVIEW_REQUIRED";
167
182
  if(o==="REVIEW")return target?Boolean(state.tickets?.[target]&&state.tickets[target]!=="REVIEW_REQUIRED"):advanced&&state.last_transition?.workflow==="review.record-review";
168
183
  if(o==="CONTINUE_PRODUCT")return normalizeStatus(state.product?.status)==="ready";
@@ -174,8 +189,8 @@ export function intentComplete(i,state,a){
174
189
  export function selectRoute({state,artifacts:a,evidence=[],reviews=[],repository,intent,routingPolicy}){
175
190
  const normal=baseRoute(state,a,evidence,reviews,repository,routingPolicy);if(!intent||intent.desired_outcome==="CONTINUE")return normal;
176
191
  if(intentComplete(intent,state,a))return{kind:"INTENT_COMPLETE",reason:intent.completion_kind};
177
- if(intent.requested_workflow&&legalNow(intent.requested_workflow,state,a)){const map={"implementation.implement-ticket":"READY","implementation.repair-ticket":"REPAIR_REQUIRED","review.review-ticket":"REVIEW_REQUIRED"},st=map[intent.requested_workflow],ticket=st&&Object.keys(state.tickets??{}).sort().find(id=>state.tickets[id]===st);return{kind:"DISPATCH_READY",workflow:intent.requested_workflow,...(ticket?{ticket}:{})};}
178
- if(["REVIEW","REPAIR"].includes(intent.desired_outcome))return{kind:"ROOT_ACTION",workflow:"orchestration.recover-interruption",reason:intent.desired_outcome+"_PRECONDITION_UNSATISFIED"};
192
+ if(intent.requested_workflow&&legalNow(intent.requested_workflow,state,a)){const map={"implementation.implement-ticket":"READY","implementation.repair-ticket":"REPAIR_REQUIRED","review.review-ticket":"REVIEW_REQUIRED"},st=map[intent.requested_workflow];let ticket=st&&Object.keys(state.tickets??{}).sort().find(id=>state.tickets[id]===st);if(intent.requested_workflow==="planning.create-tickets")ticket=currentTicketIds(state,a).find(id=>state.tickets?.[id]==="DRAFT"&&deps(id,a,state))??null;return{kind:"DISPATCH_READY",workflow:intent.requested_workflow,...(ticket?{ticket}:{})};}
193
+ if(["REVIEW","REPAIR"].includes(intent.desired_outcome))return{kind:"BLOCKED",terminal:"BLOCKED",reason:intent.desired_outcome+"_PRECONDITION_UNSATISFIED"};
179
194
  return normal;
180
195
  }
181
196
  export function applyReconciliation(state,r,repo){
@@ -3,22 +3,21 @@ import {spawnSync} from "node:child_process";
3
3
  import {mkdir,readFile,readdir,rm,writeFile} from "node:fs/promises";
4
4
  import {basename,join,resolve} from "node:path";
5
5
  import {applyReconciliation,handoffBasis,planNext} from "./orchestration-engine.mjs";
6
+ import {parseFrontmatter} from "./frontmatter.mjs";
6
7
 
7
8
  const argv=process.argv.slice(2);let workspaceArg=".",mode="prepare",invokeSkill=null;
8
9
  for(let i=0;i<argv.length;i++){if(argv[i]==="--workspace"&&argv[i+1])workspaceArg=argv[++i];else if(argv[i]==="--inspect-only")mode="inspect";else if(argv[i]==="--check-handoff")mode="check";else if(argv[i]==="--reconcile-one")mode="reconcile";else if(argv[i]==="--invoke-skill"&&argv[i+1])invokeSkill=argv[++i];else{process.stderr.write("usage: orchestration-runtime.mjs [--workspace <path>] [--invoke-skill <id>] [--inspect-only|--check-handoff|--reconcile-one]\n");process.exit(2);}}
9
10
  const workspace=resolve(workspaceArg),systemRoot=join(workspace,".yaaw-core","system"),projectRoot=join(workspace,".yaaw-core","project"),runtimeRoot=join(workspace,".yaaw-core","runtime");
10
11
  const observedPath=join(runtimeRoot,"observed-state.json"),handoffPath=join(runtimeRoot,"handoff.json"),intentPath=join(runtimeRoot,"intent.json"),statePath=join(projectRoot,"state.json");
11
12
  const json=async p=>JSON.parse(await readFile(p,"utf8"));const maybe=async p=>{try{return await json(p)}catch{return null}};const uniq=a=>[...new Set(a.filter(Boolean))];
12
- function scalar(s){s=s.trim();if(s==="null")return null;if(s==="true")return true;if(s==="false")return false;if(/^-?\d+$/.test(s))return Number(s);if(s.startsWith("[")||s.startsWith("{"))try{return JSON.parse(s)}catch{}return s.replace(/^["']|["']$/g,"")}
13
- async function fm(path){try{const l=(await readFile(path,"utf8")).split(/\r?\n/);if(l[0]?.trim()!=="---")return null;const n=l.slice(1).findIndex(x=>x.trim()==="---");if(n<0)return null;const d={};for(const x of l.slice(1,n+1)){if(!x.trim()||/^\s/.test(x)||!x.includes(":"))continue;const i=x.indexOf(":");d[x.slice(0,i).trim()]=scalar(x.slice(i+1));}return d}catch{return null}}
14
- async function review(path){try{const l=(await readFile(path,"utf8")).split(/\r?\n/);const n=l.slice(1).findIndex(x=>x.trim()==="---");if(l[0]?.trim()!=="---"||n<0)return null;const d={repository:{}};let repo=false;for(const x of l.slice(1,n+1)){if(/^repository:\s*$/.test(x)){repo=true;continue}const m=x.match(/^\s{2}([A-Za-z0-9_]+):\s*(.*)$/);if(repo&&m){d.repository[m[1]]=scalar(m[2]);continue}if(!/^\S/.test(x))continue;repo=false;const t=x.match(/^([A-Za-z0-9_]+):\s*(.*)$/);if(t)d[t[1]]=scalar(t[2]);}return d}catch{return null}}
13
+ async function fm(path){try{return parseFrontmatter(await readFile(path,"utf8"))}catch{return null}}
15
14
  async function list(dir,re){try{return(await readdir(dir,{withFileTypes:true})).filter(x=>x.isFile()&&re.test(x.name)).map(x=>join(dir,x.name)).sort()}catch{return[]}}
16
15
  function run(tool,args){const r=spawnSync(process.execPath,[tool,...args],{cwd:workspace,encoding:"utf8",windowsHide:true,maxBuffer:64*1024*1024});try{return JSON.parse(r.stdout||"{}")}catch{throw new Error((r.stderr||"").trim()||"invalid JSON from "+basename(tool))}}
17
16
  function framework(raw,status=raw?.status??"UNKNOWN"){return{yaaw_version:String(raw?.package_version??"unknown"),system_schema:Number(raw?.system_schema??1),installation_schema:Number(raw?.installation_schema??1),project_schema:Number(raw?.project_schema??1),manifest_digest:String(raw?.manifest_digest??"sha256:unknown"),integrity_status:status,modified:Array.isArray(raw?.modified)?raw.modified:[],missing:Array.isArray(raw?.missing)?raw.missing:[],local_overrides:Array.isArray(raw?.local_overrides)?raw.local_overrides:[],repair_required:status!=="HEALTHY"}}
18
17
  function repo(raw){return{schema:"yaaw.repository-identity/v2",algorithm:raw?.algorithm??null,status:raw?.status??"UNAVAILABLE",workspace_scope:String(raw?.workspace_scope??"."),git_root_relation:raw?.git_root_relation??"unknown",head_commit:raw?.head_commit??null,dirty:typeof raw?.dirty==="boolean"?raw.dirty:null,worktree_digest:raw?.worktree_digest??null,components:raw?.components??null,changed_paths:Array.isArray(raw?.changed_paths)?raw.changed_paths:[],error:raw?.error??null}}
19
18
  async function contracts(){const names=["workflows","execution-policy","role-io","routing-policy","handoff-policy","artifacts","skills","transitions","reconciliation-policy"];const vals=await Promise.all(names.map(n=>json(join(systemRoot,"registries",n+".json"))));const c=Object.fromEntries(names.map((n,i)=>[n.replaceAll("-","_"),vals[i]])),errors=[];for(const[w,p]of Object.entries(c.handoff_policy.workflows??{})){const reg=c.workflows[w],ex=c.execution_policy.workflows?.[w],io=c.role_io.roles?.[reg?.role];if(!reg)errors.push("unknown workflow "+w);if(!ex)errors.push("no execution policy "+w);if(!io)errors.push("unknown role "+reg?.role);if(io){const br=(p.reads??[]).filter(x=>!io.reads.includes(x)),bw=(p.writes??[]).filter(x=>!io.writes.includes(x));if(br.length)errors.push(w+" bad reads "+br);if(bw.length)errors.push(w+" bad writes "+bw)}}return{...c,errors}}
20
19
  const rel=p=>p.replace(workspace+"/","").replaceAll("\\","/"),specId=v=>String(v??"").match(/SPEC-[0-9]+/)?.[0]??null;
21
- async function inspect(state){const pp=join(projectRoot,"product.md"),ep=join(projectRoot,"engineering.md"),specs={},tickets={},reviews=[],evidence=[];for(const p of await list(join(projectRoot,"specs"),/^SPEC-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)specs[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"tickets"),/^TASK-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)tickets[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"reviews"),/^TASK-[0-9]+-R[0-9]+\.md$/)){const v=await review(p);if(v)reviews.push({path:rel(p),value:v})}for(const p of await list(join(projectRoot,"evidence"),/\.json$/)){try{evidence.push({path:rel(p),value:await json(p)})}catch{}}return{product:{path:rel(pp),meta:await fm(pp)},engineering:{path:rel(ep),meta:await fm(ep)},specs,tickets,reviews,evidence,research:(await list(join(projectRoot,"research"),/^RSH-.*\.md$/)).map(rel),rules:(await list(join(projectRoot,"rules"),/\.md$/)).map(rel),activeSpecId:specId(state?.planning?.active_spec)}}
20
+ async function inspect(state){const pp=join(projectRoot,"product.md"),ep=join(projectRoot,"engineering.md"),specs={},tickets={},reviews=[],evidence=[];for(const p of await list(join(projectRoot,"specs"),/^SPEC-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)specs[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"tickets"),/^TASK-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)tickets[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"reviews"),/^TASK-[0-9]+-R[0-9]+\.md$/)){const v=await fm(p);if(v)reviews.push({path:rel(p),value:v})}for(const p of await list(join(projectRoot,"evidence"),/\.json$/)){try{evidence.push({path:rel(p),value:await json(p)})}catch{}}return{product:{path:rel(pp),meta:await fm(pp)},engineering:{path:rel(ep),meta:await fm(ep)},specs,tickets,reviews,evidence,research:(await list(join(projectRoot,"research"),/^RSH-.*\.md$/)).map(rel),rules:(await list(join(projectRoot,"rules"),/\.md$/)).map(rel),activeSpecId:specId(state?.planning?.active_spec)}}
22
21
  function summary(a){return{product:a.product.meta?a.product:null,engineering:a.engineering.meta?a.engineering:null,specs:a.specs,tickets:a.tickets,reviews:a.reviews.map(x=>x.path),evidence:a.evidence.map(x=>x.path)}}
23
22
  function requirement(r,repository){return r==="NONE"||r==="INSPECT"&&["READY","UNVERSIONED"].includes(repository.status)||r==="IDENTITY"&&repository.status==="READY"}
24
23
  function refs(policy,a,ticket,state){const out=[];for(const x of policy.reads??[]){if(x==="product"&&a.product.meta)out.push(a.product.path);else if(x==="engineering"&&a.engineering.meta)out.push(a.engineering.path);else if(x==="spec"){const id=specId(state?.planning?.active_spec);if(id&&a.specs[id])out.push(a.specs[id].path)}else if(x==="ticket"&&ticket&&a.tickets[ticket])out.push(a.tickets[ticket].path);else if(x==="evidence")out.push(...a.evidence.map(v=>v.path));else if(x==="review")out.push(...a.reviews.map(v=>v.path));else if(x==="engineering_research")out.push(...a.research);else if(x==="project_rule")out.push(...a.rules);else if(x==="state")out.push(".yaaw-core/project/state.json")}return uniq(out)}
@@ -14,7 +14,7 @@ The runtime reads the machine routing/execution/role-I/O/handoff policies, curre
14
14
 
15
15
  - `DISPATCH_READY`: consume the persisted handoff; do not rebuild it.
16
16
  - `RECONCILE_REQUIRED`: execute only `orchestration.reconcile-state` before preparing again.
17
- - `ROOT_ACTION`: execute only the selected Orchestrator recovery workflow.
17
+ - `ROOT_ACTION`: execute only the selected Orchestrator recovery workflow. This result is valid only for a proven interruption boundary; ordinary `DRAFT` admission routes to Planner.
18
18
  - `TERMINAL`/`BLOCKED`/`FRAMEWORK_STOP`: write no executable handoff.
19
19
 
20
20
  ## Boundary
@@ -1,18 +1,32 @@
1
1
  # Recover interruption
2
2
 
3
3
  ## Purpose
4
- Resume safely after context/session failure without duplicating already-completed work.
4
+ Resume safely after an actual context/session failure without duplicating already-completed work.
5
+
6
+ This workflow is interruption-specific. It is not the generic fallback for an unresolved lifecycle state, a `DRAFT` ticket, missing planning admission, or a shortcut precondition failure.
5
7
 
6
8
  ## Inputs
7
9
  Reconciled state, durable artifacts, implementation-start/verification evidence, reviews, and repository identity.
8
10
 
11
+ ## Selection guard
12
+ Select `orchestration.recover-interruption` only when durable evidence indicates an interrupted execution boundary that cannot yet be classified by the ordinary semantic routes. The normal example is `IN_PROGRESS` with missing implementation-start provenance after a worker/session loss.
13
+
14
+ Do not select recovery merely because no Implementer handoff is currently available.
15
+
9
16
  ## Procedure
10
17
  1. Discard stale runtime caches/handoffs.
11
18
  2. Identify the last trustworthy durable boundary.
12
19
  3. For `IN_PROGRESS`: current PASS verification is reconciled to review; valid start evidence without PASS verification routes to `implementation.verify-ticket`; missing start evidence requires explicit recovery/blocking proof.
13
20
  4. Never rerun full implementation merely because a worker response disappeared.
14
21
  5. A source-current review artifact is adopted before another Reviewer dispatch.
15
- 6. Use only legal transitions with provenance; if the boundary cannot be proven, return `BLOCKED`.
22
+ 6. Use only legal transitions with provenance; if the interruption boundary cannot be proven, return an exact typed blocker rather than repeatedly selecting recovery.
23
+ 7. If inspection proves there is no interrupted execution boundary, return immediately to `orchestration.route`. Do not remain parked in recovery.
24
+
25
+ ## Explicit non-cases
26
+ - registered `DRAFT` ticket with current planning/spec sources -> Planner admission;
27
+ - `DRAFT` whose prerequisite ticket is runnable -> continue the prerequisite;
28
+ - all accepted work PASS/CANCELLED with scope OPEN/UNKNOWN -> Planner next frontier;
29
+ - direct Review/Repair shortcut with unsatisfied lifecycle preconditions -> typed shortcut precondition failure.
16
30
 
17
31
  ## Output
18
- A safe next boundary or exact blocker, then control returns to `orchestration.route`.
32
+ A safe next boundary or exact interruption blocker, then control returns to `orchestration.route`.
@@ -19,8 +19,8 @@ Repeat:
19
19
  1. run deterministic preparation once;
20
20
  2. on `FRAMEWORK_STOP`, stop without lifecycle mutation;
21
21
  3. on `RECONCILE_REQUIRED`, apply exactly one reconciliation with `--reconcile-one`, then observe again;
22
- 4. on `ROOT_ACTION`, execute only the returned Orchestrator recovery workflow, then observe again;
23
- 5. on `DISPATCH_READY`, execute exactly the persisted handoff once;
22
+ 4. on `ROOT_ACTION`, execute only the returned Orchestrator recovery workflow, then observe again; `ROOT_ACTION` is reserved for evidence-backed interruption recovery, never ordinary `DRAFT` admission;
23
+ 5. on `DISPATCH_READY`, execute exactly the persisted handoff once; a dependency-satisfied current `DRAFT` routes to Planner admission, not recovery;
24
24
  6. after any worker outcome or disappearance, discard worker text as authority and observe durable reality again;
25
25
  7. on `INTENT_COMPLETE`, `TERMINAL`, `BLOCKED`, or human input, stop.
26
26
 
@@ -1,23 +1,50 @@
1
1
  # Create tickets
2
2
 
3
3
  ## Purpose
4
- Translate one current accepted spec into bounded dependency-aware implementation contracts.
4
+ Create bounded dependency-aware implementation contracts for one current accepted spec, and re-evaluate already-registered `DRAFT` tickets when their admission prerequisites may have changed.
5
5
 
6
6
  ## Preconditions
7
- Source spec is `ACCEPTED`, product/engineering revisions remain current, and repository requirement `IDENTITY` is satisfied with repository status `READY`.
7
+ Source spec is `ACCEPTED`, product/engineering revisions remain current, planning status is `ready` with readiness `PASS`, and repository requirement `IDENTITY` is satisfied with repository status `READY`.
8
+
9
+ ## Existing-ticket rule
10
+ Before allocating any new `TASK-NNN` ID, inspect current tickets for the active spec. A current `DRAFT` is an admission candidate, not evidence that ticket creation must start over.
11
+
12
+ Never duplicate an existing ticket merely because it is still `DRAFT`.
8
13
 
9
14
  ## Procedure
10
15
  1. If exact repository identity is unavailable, return `PRECONDITION_UNSATISFIED:REPOSITORY_IDENTITY_UNAVAILABLE`; do not admit executable tickets.
11
- 2. Split work into coherent `TASK-NNN` units sized for a fresh Implementer.
12
- 3. Apply `.yaaw-core/system/rules/changeability.md` while defining boundaries.
13
- 4. Each ticket metadata records source spec/revision, product revision, engineering decision IDs, dependencies, expertise, ticket revision, and status.
14
- 5. Body records product requirements, relevant areas, required behavior, allowed scope, non-goals, acceptance criteria, required tests, and relevant engineering/changeability constraints.
15
- 6. Ensure supporting refactors are admitted only when necessary for safe implementation or verification.
16
- 7. Validate ticket template/metadata.
17
- 8. Set `READY` only when dependencies, planning admission, and repository identity are current; otherwise `DRAFT`.
16
+ 2. Inspect current ticket artifacts and reconciled lifecycle state for the active spec before creating anything.
17
+ 3. If routing supplied a current `DRAFT` ticket, re-evaluate that ticket first.
18
+ 4. A `DRAFT` may become `READY` only when:
19
+ - its product, engineering, spec, and ticket revisions are current;
20
+ - planning remains `ready` with readiness `PASS`;
21
+ - every declared dependency is `PASS` in reconciled state;
22
+ - repository identity is current and trustworthy;
23
+ - its acceptance contract remains implementable without inventing product or engineering decisions; and
24
+ - any explicit execution prerequisite recorded in its status rationale is currently satisfied.
25
+ 5. If the contract itself must change, do not disguise that as admission. Route through `planning.replan`, revise the contract with proper revision/invalidation semantics, then reassess admission.
26
+ 6. If the contract is unchanged and admission now passes, update only the ticket artifact status from `DRAFT` to `READY`. Do not rewrite history and do not increment ticket revision solely for the lifecycle admission bit.
27
+ 7. If dependencies are not yet `PASS`, keep the ticket `DRAFT`. Normal orchestration should continue the prerequisite ticket rather than repeatedly invoking Planner.
28
+ 8. For genuinely missing work, split the accepted spec into coherent `TASK-NNN` units sized for a fresh Implementer.
29
+ 9. Apply `.yaaw-core/system/rules/changeability.md` while defining boundaries.
30
+ 10. Each ticket metadata records source spec/revision, product revision, engineering decision IDs, dependencies, expertise, ticket revision, and status.
31
+ 11. Body records product requirements, relevant areas, required behavior, allowed scope, non-goals, acceptance criteria, required tests, relevant engineering/changeability constraints, and a precise status rationale when left `DRAFT`.
32
+ 12. Ensure supporting refactors are admitted only when necessary for safe implementation or verification.
33
+ 13. Validate ticket template/metadata before returning success.
34
+
35
+ ## Durable admission fact
36
+ Planner owns the semantic decision that a ticket artifact is `READY`. Planner still never writes `state.json`.
37
+
38
+ After Planner persists `DRAFT -> READY` in the current ticket artifact, Orchestrator must observe that fact, validate the legal transition and current sources/dependencies/repository identity, then adopt exactly one `TICKET_ADMISSION` reconciliation into state.
39
+
40
+ Ticket frontmatter therefore does not directly override state. It proposes a Planner-owned durable fact that Orchestrator may adopt only when the admission contract is still valid.
18
41
 
19
42
  ## Output
20
- Dependency-aware tickets requiring no planning-chat memory and preserving focused, reviewable change boundaries.
43
+ Dependency-aware tickets requiring no planning-chat memory, plus one of:
44
+ - newly-created current ticket artifacts;
45
+ - an existing current `DRAFT` promoted to `READY`;
46
+ - an unchanged `DRAFT` with an exact prerequisite reason; or
47
+ - a typed prerequisite/blocker/replan result.
21
48
 
22
49
  ## Registration
23
- Planner writes ticket artifacts only. Orchestrator registers current tickets into `state.json` one reconciliation at a time, preventing duplicate ticket creation when Planner output text is lost.
50
+ Planner writes ticket artifacts only. Orchestrator registers new current tickets and adopts later Planner admission facts into `state.json` one reconciliation at a time, preventing duplicate ticket creation and preventing lifecycle jumps.
@@ -18,8 +18,9 @@ Do not open candidate planning workflow bodies before one route is selected.
18
18
  6. unresolved human-owned engineering decisions -> `planning.question-round`;
19
19
  7. readiness not current -> `planning.readiness-review`;
20
20
  8. readiness PASS but no current accepted spec -> `planning.create-spec`;
21
- 9. accepted spec lacks admitted tickets -> `planning.create-tickets`;
22
- 10. otherwise return planning frontier `READY`.
21
+ 9. accepted spec lacks tickets, or a current dependency-satisfied `DRAFT` needs admission re-evaluation -> `planning.create-tickets`;
22
+ 10. current `DRAFT` tickets whose dependencies are not yet `PASS` do not preempt runnable implementation/review work; normal orchestration continues their prerequisites;
23
+ 11. otherwise return planning frontier `READY`.
23
24
 
24
25
  After each selected workflow persists durable output, return to routing before loading another workflow.
25
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaaw-se",
3
- "version": "0.3.9",
3
+ "version": "0.3.11-dev.36385821931.1",
4
4
  "description": "Artifact-first autonomous software engineering workflow installer.",
5
5
  "type": "module",
6
6
  "bin": {