yaaw-se 0.3.13 → 0.3.14-dev.39042534464.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.
- package/dist/payload/payload-files.json +14 -19
- package/dist/payload/payload.json +1 -1
- package/dist/payload/yaaw-core/system/SYSTEM.md +0 -4
- package/dist/payload/yaaw-core/system/engine/routing.mjs +15 -19
- package/dist/payload/yaaw-core/system/engine/runtime.mjs +4 -13
- package/dist/payload/yaaw-core/system/kernel.yaml +0 -3
- package/dist/payload/yaaw-core/system/roles/planner.md +6 -2
- package/dist/payload/yaaw-core/system/roles/prd.md +3 -3
- package/package.json +2 -5
- package/dist/payload/yaaw-core/system/engine/contracts.mjs +0 -147
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "payload.json",
|
|
31
|
-
"sha256": "
|
|
32
|
-
"bytes":
|
|
31
|
+
"sha256": "cbc20c34ecb09bd67b21cb3714a5e54459cc7cbd306b708a67bfaca7e0563de5",
|
|
32
|
+
"bytes": 75
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "skills/yaaw-create-spec/SKILL.md",
|
|
@@ -96,11 +96,6 @@
|
|
|
96
96
|
"sha256": "6189616f6ddfb5992dcc22e562f2f2c061ce5f3202c09dfce0f5c9adef5bf42f",
|
|
97
97
|
"bytes": 1247
|
|
98
98
|
},
|
|
99
|
-
{
|
|
100
|
-
"path": "yaaw-core/system/engine/contracts.mjs",
|
|
101
|
-
"sha256": "b9541c15dcbb842da1812876c862e51b9a4422fcead81b274c19a864bf0885b3",
|
|
102
|
-
"bytes": 10180
|
|
103
|
-
},
|
|
104
99
|
{
|
|
105
100
|
"path": "yaaw-core/system/engine/frontmatter.mjs",
|
|
106
101
|
"sha256": "2c90097563a91c52e6f9c7748dac583e67967fd4a9748e393bf61d753594277e",
|
|
@@ -118,18 +113,18 @@
|
|
|
118
113
|
},
|
|
119
114
|
{
|
|
120
115
|
"path": "yaaw-core/system/engine/routing.mjs",
|
|
121
|
-
"sha256": "
|
|
122
|
-
"bytes":
|
|
116
|
+
"sha256": "1e20ba87e76878aac6047d0ee62dbc55a24ee20a6a2551d36a803e892d32f170",
|
|
117
|
+
"bytes": 13250
|
|
123
118
|
},
|
|
124
119
|
{
|
|
125
120
|
"path": "yaaw-core/system/engine/runtime.mjs",
|
|
126
|
-
"sha256": "
|
|
127
|
-
"bytes":
|
|
121
|
+
"sha256": "dc30505164950e81ad6343b4c8d04cfe3604bfc84c8d4aec36eff424c614e614",
|
|
122
|
+
"bytes": 12293
|
|
128
123
|
},
|
|
129
124
|
{
|
|
130
125
|
"path": "yaaw-core/system/kernel.yaml",
|
|
131
|
-
"sha256": "
|
|
132
|
-
"bytes":
|
|
126
|
+
"sha256": "7e93d186484bebeed95ac0ddc68f489b198fbf94a6a222c92f577cd3b55cba41",
|
|
127
|
+
"bytes": 21373
|
|
133
128
|
},
|
|
134
129
|
{
|
|
135
130
|
"path": "yaaw-core/system/modules/changeability.md",
|
|
@@ -168,13 +163,13 @@
|
|
|
168
163
|
},
|
|
169
164
|
{
|
|
170
165
|
"path": "yaaw-core/system/roles/planner.md",
|
|
171
|
-
"sha256": "
|
|
172
|
-
"bytes":
|
|
166
|
+
"sha256": "e12f34801181410064793530446578084c4e4fb4da944e60444875f3393f8798",
|
|
167
|
+
"bytes": 8646
|
|
173
168
|
},
|
|
174
169
|
{
|
|
175
170
|
"path": "yaaw-core/system/roles/prd.md",
|
|
176
|
-
"sha256": "
|
|
177
|
-
"bytes":
|
|
171
|
+
"sha256": "437c64a23575d353a86f8c81761394db99e855905c4f47beb6aad1dfa2f0efd6",
|
|
172
|
+
"bytes": 3372
|
|
178
173
|
},
|
|
179
174
|
{
|
|
180
175
|
"path": "yaaw-core/system/roles/reviewer.md",
|
|
@@ -288,8 +283,8 @@
|
|
|
288
283
|
},
|
|
289
284
|
{
|
|
290
285
|
"path": "yaaw-core/system/SYSTEM.md",
|
|
291
|
-
"sha256": "
|
|
292
|
-
"bytes":
|
|
286
|
+
"sha256": "b793fbfae235c63861d7b131ff2b379c8dae3abc8fad85be3d089f1261d61a73",
|
|
287
|
+
"bytes": 9772
|
|
293
288
|
},
|
|
294
289
|
{
|
|
295
290
|
"path": "yaaw-core/system/templates/dispatch-failures.json",
|
|
@@ -82,10 +82,6 @@ Workers never privately command or spawn peer YAAW authorities. After every work
|
|
|
82
82
|
|
|
83
83
|
Before semantic routing, verify the installer manifest and package-managed system bytes. `MODIFIED`, `MISSING`, `LOCAL_OVERRIDE`, unknown integrity, or contract inconsistency fails closed. Semantic workers never edit `.yaaw-core/system/**` to unblock themselves.
|
|
84
84
|
|
|
85
|
-
Dispatch completeness is a framework invariant. A worker dispatch target is the union of non-Orchestrator public-skill requests, deterministic router targets (the values used by router branches), and declared worker handoffs. Other role operations are internal suboperations; root Orchestrator operations do not require worker handoffs. Every target must resolve a workflow, owning role, valid handoff, role-bounded reads/writes, active artifact, authorized existing modules, and the typed worker result protocol. Public skill metadata and completion vocabulary must agree with the kernel. `engine/contracts.mjs` is the canonical validator used by core, distribution, payload generation/verification, and runtime. Missing contracts stop as framework defects, never consumer semantic blockers.
|
|
86
|
-
|
|
87
|
-
Product revision changes the downstream product basis. Orchestrator derives stale ticket authority and performs only legal status reconciliation; Planner owns changed downstream contracts. Engineering readiness based on an older product revision cannot authorize a specification. Clarity-only product refinement preserves semantic revision and downstream source authority.
|
|
88
|
-
|
|
89
85
|
## Repository identity
|
|
90
86
|
|
|
91
87
|
Repository identity is produced only by `.yaaw-core/system/engine/repository-identity.mjs`; roles do not reimplement it. Runtime caches, ticket lifecycle files, immutable implementation evidence, and immutable reviews are excluded from the application worktree digest because their normal lifecycle writes would otherwise invalidate their own basis. Ticket semantic freshness is instead bound explicitly by ticket revision and ticket content digest in handoffs/evidence/reviews.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ROUTES, dispatchableWorkflows } from "./contracts.mjs";
|
|
2
1
|
const ACTIVE = new Set(["READY","IN_PROGRESS","REVIEW_REQUIRED","REPAIR_REQUIRED","PASS"]);
|
|
3
2
|
const TERMINAL = new Set(["PASS","CANCELLED"]);
|
|
4
3
|
const REVIEW_RESULT = { PASS:"PASS", REPAIR:"REPAIR_REQUIRED", REPLAN:"REPLAN_REQUIRED", BLOCKED:"BLOCKED" };
|
|
@@ -29,7 +28,6 @@ export function deriveState(a){
|
|
|
29
28
|
const p=a.product?.meta??{},e=a.engineering?.meta??{};
|
|
30
29
|
const product={artifact:a.product?.path??null,status:String(p.status??"missing"),revision:Number(p.revision??0)};
|
|
31
30
|
const planning={artifact:a.engineering?.path??null,status:String(e.status??"missing"),revision:Number(e.revision??0),current_frontier:e.current_frontier??null,readiness:e.readiness??"pending",scope_status:e.scope_status??"UNKNOWN",active_spec:null};
|
|
32
|
-
if(e.product_revision!==undefined&&Number(e.product_revision)!==product.revision)planning.readiness="pending";
|
|
33
31
|
const accepted=Object.entries(a.specs??{}).filter(([,x])=>x.meta?.status==="ACCEPTED"&&Number(x.meta.product_revision??0)===product.revision&&Number(x.meta.engineering_revision??0)===planning.revision).sort(([x],[y])=>byId(x,y));
|
|
34
32
|
if(accepted.length===1)planning.active_spec=accepted[0][1].path;
|
|
35
33
|
const tickets=Object.fromEntries(Object.entries(a.tickets??{}).map(([id,x])=>[id,String(x.meta?.status??"DRAFT")]));
|
|
@@ -88,7 +86,7 @@ export function intentComplete(intent,state,a){
|
|
|
88
86
|
if(!intent)return false;const o=intent.desired_outcome,target=requestedTicket(intent,state),base=intent.created_from??{};
|
|
89
87
|
if(o==="CONTINUE")return false;
|
|
90
88
|
if(o==="CONTINUE_PRODUCT")return state.product.status==="ready";
|
|
91
|
-
if(o==="REVISE_PRODUCT")return
|
|
89
|
+
if(o==="REVISE_PRODUCT")return Number(state.product.revision)>Number(base.product_revision??0);
|
|
92
90
|
if(o==="REFINE_PRODUCT")return Boolean(a.product?.digest&&base.product_digest&&a.product.digest!==base.product_digest);
|
|
93
91
|
if(o==="CONTINUE_PLANNING")return planningReady(state);
|
|
94
92
|
if(o==="PLANNING_REVIEW")return state.planning.readiness!==base.planning_readiness||a.engineering?.digest!==base.engineering_digest;
|
|
@@ -100,24 +98,23 @@ export function intentComplete(intent,state,a){
|
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
function baseRoute(state,a){
|
|
103
|
-
if(state.product.status!=="ready")return {workflow:
|
|
104
|
-
const replan=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REPLAN_REQUIRED");if(replan)return{workflow:
|
|
105
|
-
if(state.planning.status!=="ready"||state.planning.readiness!=="PASS")return{workflow:
|
|
106
|
-
if(!currentSpecId(state))return{workflow:
|
|
101
|
+
if(state.product.status!=="ready")return {workflow:"prd.route",ticket:null};
|
|
102
|
+
const replan=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REPLAN_REQUIRED");if(replan)return{workflow:"planning.replan",ticket:replan};
|
|
103
|
+
if(state.planning.status!=="ready"||state.planning.readiness!=="PASS")return{workflow:"planning.route",ticket:null};
|
|
104
|
+
if(!currentSpecId(state))return{workflow:"planning.create-spec",ticket:null};
|
|
107
105
|
const ids=currentTicketIds(state,a);
|
|
108
|
-
const repair=ids.find(id=>state.tickets[id]==="REPAIR_REQUIRED");if(repair)return{workflow:
|
|
109
|
-
const review=ids.find(id=>state.tickets[id]==="REVIEW_REQUIRED");if(review)return{workflow:
|
|
110
|
-
const progress=ids.find(id=>state.tickets[id]==="IN_PROGRESS");if(progress)return{workflow:
|
|
111
|
-
const ready=ids.find(id=>state.tickets[id]==="READY"&&dependenciesPass(id,state,a));if(ready)return{workflow:
|
|
112
|
-
const draft=ids.find(id=>state.tickets[id]==="DRAFT"&&dependenciesPass(id,state,a));if(draft)return{workflow:
|
|
113
|
-
if(ids.length===0)return{workflow:
|
|
114
|
-
if(ids.every(id=>TERMINAL.has(state.tickets[id]))){if(state.planning.scope_status==="COMPLETE")return{terminal:"COMPLETE"};return{workflow:
|
|
106
|
+
const repair=ids.find(id=>state.tickets[id]==="REPAIR_REQUIRED");if(repair)return{workflow:"implementation.repair-ticket",ticket:repair};
|
|
107
|
+
const review=ids.find(id=>state.tickets[id]==="REVIEW_REQUIRED");if(review)return{workflow:"review.review-ticket",ticket:review};
|
|
108
|
+
const progress=ids.find(id=>state.tickets[id]==="IN_PROGRESS");if(progress)return{workflow:"implementation.verify-ticket",ticket:progress};
|
|
109
|
+
const ready=ids.find(id=>state.tickets[id]==="READY"&&dependenciesPass(id,state,a));if(ready)return{workflow:"implementation.implement-ticket",ticket:ready};
|
|
110
|
+
const draft=ids.find(id=>state.tickets[id]==="DRAFT"&&dependenciesPass(id,state,a));if(draft)return{workflow:"planning.create-tickets",ticket:draft};
|
|
111
|
+
if(ids.length===0)return{workflow:"planning.create-tickets",ticket:null};
|
|
112
|
+
if(ids.every(id=>TERMINAL.has(state.tickets[id]))){if(state.planning.scope_status==="COMPLETE")return{terminal:"COMPLETE"};return{workflow:"planning.route",ticket:null};}
|
|
115
113
|
const blocked=ids.find(id=>state.tickets[id]==="BLOCKED");if(blocked)return{blocked:`ticket ${blocked} is BLOCKED`,ticket:blocked};
|
|
116
114
|
return{blocked:"no legal next workflow from current durable artifacts",ticket:null};
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
function legalRequested(
|
|
120
|
-
if(!dispatchableWorkflows(kernel).has(workflow))return false;
|
|
117
|
+
function legalRequested(workflow,ticket,state,a){
|
|
121
118
|
if(workflow==="prd.route"||workflow==="prd.revise"||workflow==="prd.refine")return true;
|
|
122
119
|
if(workflow==="planning.route"||workflow==="planning.readiness-review")return state.product.status==="ready";
|
|
123
120
|
if(workflow==="planning.create-spec")return planningReady(state)&&!currentSpecId(state);
|
|
@@ -131,14 +128,13 @@ function legalRequested(kernel,workflow,ticket,state,a){
|
|
|
131
128
|
export function planNext(kernel,state,a,repo,intent){
|
|
132
129
|
const reconciliation=selectReconciliation(kernel,state,a,repo);if(reconciliation)return{type:"RECONCILE_REQUIRED",reconciliation};
|
|
133
130
|
if(intentComplete(intent,state,a))return{type:"INTENT_COMPLETE",completion_kind:intent.completion_kind};
|
|
134
|
-
if(state.phase==="complete"
|
|
135
|
-
if(intent?.desired_outcome==="REVISE_PRODUCT"&&Number(state.product.revision)>Number(intent.created_from?.product_revision??0)&&state.product.status!=="ready")return{type:"ROUTE",workflow:ROUTES.product,ticket:null};
|
|
131
|
+
if(state.phase==="complete")return{type:"TERMINAL",terminal:"COMPLETE"};
|
|
136
132
|
if(intent&&intent.requested_workflow&&intent.requested_workflow!=="orchestration.route"){
|
|
137
133
|
let ticket=requestedTicket(intent,state);
|
|
138
134
|
if(!ticket&&intent.desired_outcome==="IMPLEMENT")ticket=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="READY"&&dependenciesPass(id,state,a))??null;
|
|
139
135
|
if(!ticket&&intent.desired_outcome==="REPAIR")ticket=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REPAIR_REQUIRED")??null;
|
|
140
136
|
if(!ticket&&intent.desired_outcome==="REVIEW")ticket=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REVIEW_REQUIRED")??null;
|
|
141
|
-
if(legalRequested(
|
|
137
|
+
if(legalRequested(intent.requested_workflow,ticket,state,a))return{type:"ROUTE",workflow:intent.requested_workflow,ticket};
|
|
142
138
|
return{type:"BLOCKED",reason:`PRECONDITION_UNSATISFIED:${intent.requested_workflow}`,ticket};
|
|
143
139
|
}
|
|
144
140
|
return{type:"ROUTE",...baseRoute(state,a)};
|
|
@@ -3,7 +3,6 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join, relative, resolve } from "node:path";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
-
import { validateContracts, validateDispatchFiles } from "./contracts.mjs";
|
|
7
6
|
import { parseFrontmatter } from "./frontmatter.mjs";
|
|
8
7
|
import { deriveState, planNext, chooseIntentTarget } from "./routing.mjs";
|
|
9
8
|
|
|
@@ -36,7 +35,7 @@ function refsFor(reads,state,a,ticket){const refs=[];const push=x=>{if(x&&!refs.
|
|
|
36
35
|
function revisions(state,a,ticket){const spec=selectedSpec(state,a,ticket);return{product:Number(state.product.revision??0),engineering:Number(state.planning.revision??0),spec:Number(spec?.meta?.revision??0),ticket:Number(ticket?a.tickets?.[ticket]?.meta?.revision??0:0)}}
|
|
37
36
|
function basis(state,a,ticket){const spec=selectedSpec(state,a,ticket);return{product_digest:a.product?.digest??null,engineering_digest:a.engineering?.digest??null,spec_digest:spec?.digest??null,ticket_digest:ticket?a.tickets?.[ticket]?.digest??null:null,ticket_status:ticket?state.tickets?.[ticket]??null:null}}
|
|
38
37
|
function modulesFor(kernel,role,policy,a,ticket){const names=[...(policy.required_modules??[])];if(policy.ticket_modules&&ticket)for(const x of a.tickets?.[ticket]?.meta?.expertise??[])names.push(x);return[...new Set(names)].filter(name=>kernel.modules[name]?.usable_by?.includes(role)).map(name=>kernel.modules[name].path)}
|
|
39
|
-
function handoff(kernel,framework,state,a,repo,workflow,ticket,intent){const wf=kernel.workflows[workflow],policy=kernel.handoffs[workflow],role=kernel.roles[wf?.role];if(!wf||!policy||!role)throw new Error(`no handoff contract for ${workflow}`);for(const
|
|
38
|
+
function handoff(kernel,framework,state,a,repo,workflow,ticket,intent){const wf=kernel.workflows[workflow],policy=kernel.handoffs[workflow],role=kernel.roles[wf?.role];if(!wf||!policy||!role)throw new Error(`no handoff contract for ${workflow}`);for(const w of policy.writes??[])if(!role.writes.includes(w))throw new Error(`workflow ${workflow} writes unauthorized artifact ${w}`);return{schema:"yaaw.handoff/v4",framework:frameworkBasis(framework),role:wf.role,workflow,operation:wf.operation,instruction:{document:role.document,operation:wf.operation},desired_intent:intent?.desired_outcome??null,active_artifact:policy.active_artifact??null,ticket:ticket??null,references:refsFor(policy.reads,state,a,ticket),reads:policy.reads,writes:policy.writes,forbidden_writes:Object.keys(kernel.artifacts).filter(x=>!role.writes.includes(x)),revisions:revisions(state,a,ticket),basis:basis(state,a,ticket),expertise:modulesFor(kernel,wf.role,policy,a,ticket),host_expertise:[],expected_output:policy.expected_output,result_vocabulary:policy.result_vocabulary,repository_requirement:wf.repository_requirement,repository:repo}}
|
|
40
39
|
function repoAllowed(requirement,repo){if(requirement==="NONE")return true;if(requirement==="INSPECT")return repo?.status==="READY"||repo?.status==="UNVERSIONED";return repo?.status==="READY"}
|
|
41
40
|
function patchStatus(text,status){const lines=String(text).split(/\r?\n/);if(lines[0]?.trim()!=="---")throw new Error("ticket missing frontmatter");const end=lines.slice(1).findIndex(x=>x.trim()==="---");if(end<0)throw new Error("ticket missing closing frontmatter");const idx=lines.slice(1,end+1).findIndex(x=>/^status:\s*/.test(x));if(idx<0)throw new Error("ticket missing status");lines[idx+1]=`status: ${status}`;return lines.join("\n")}
|
|
42
41
|
async function observed(root,framework,repo,state,a,reconciliation){const value={schema:"yaaw.observed-state/v4",framework:{...frameworkBasis(framework),modified:framework.modified??[],missing:framework.missing??[],local_overrides:framework.local_overrides??[],repair_required:Boolean(framework.repair_required)},repository:repo,derived:state,reconstructable:true,artifacts:{product:a.product,engineering:a.engineering,specs:a.specs,tickets:a.tickets,reviews:a.reviews,evidence:a.evidence,research:a.research,rules:a.rules},inconsistencies:[],candidate_actions:[],reconciliation};await mkdir(join(root,".yaaw-core","runtime"),{recursive:true});await writeFile(join(root,".yaaw-core","runtime","observed-state.json"),JSON.stringify(value,null,2)+"\n");return value}
|
|
@@ -44,23 +43,15 @@ async function observed(root,framework,repo,state,a,reconciliation){const value=
|
|
|
44
43
|
async function main(){
|
|
45
44
|
const opt=argsOf(process.argv.slice(2)),root=resolve(opt.workspace),system=join(root,".yaaw-core","system"),runtime=join(root,".yaaw-core","runtime"),kernel=JSON.parse(await readFile(join(system,"kernel.yaml"),"utf8"));
|
|
46
45
|
const integrity=execJson(join(system,"engine","integrity.mjs"),root);if(integrity.status!=="HEALTHY"){console.log(JSON.stringify({type:"FRAMEWORK_STOP",reason:`FRAMEWORK_${integrity.status}`,framework:integrity}));return}
|
|
47
|
-
const contractErrors=validateContracts(kernel);
|
|
48
|
-
if(contractErrors.length){
|
|
49
|
-
const intent=await json(join(runtime,"intent.json")),sourceSkill=opt.invoke??intent?.source_skill??null,requested=kernel.skills[sourceSkill]?.requested_workflow??intent?.requested_workflow??null;
|
|
50
|
-
const subject=contractErrors[0].split(": ")[0],workflow=kernel.workflows[subject]?subject:requested??subject;
|
|
51
|
-
throw Object.assign(new Error(contractErrors.join("; ")),{diagnostic:{classification:"FRAMEWORK_DEFECT_OR_DAMAGED_INSTALLATION",workflow,requested_workflow:requested,source_skill:sourceSkill,routing_source:sourceSkill?"PUBLIC_SKILL_INTENT":"DETERMINISTIC_ROUTER",owning_role:kernel.workflows[workflow]?.role??null,framework_version:integrity.package_version,contract_type:"DISPATCH_CONTRACT",recommendation:"Update or repair YAAW-SE through the supported installer; preserve project memory."}});
|
|
52
|
-
}
|
|
53
46
|
const repo=execJson(join(system,"engine","repository-identity.mjs"),root),a=await inspectArtifacts(root),state=deriveState(a);
|
|
54
47
|
let intent=await json(join(runtime,"intent.json"));if(opt.invoke)intent=await seedIntent(root,kernel,opt.invoke,state,a);
|
|
55
48
|
const planned=planNext(kernel,state,a,repo,intent),reconciliation=planned.type==="RECONCILE_REQUIRED"?planned.reconciliation:null;await observed(root,integrity,repo,state,a,reconciliation);
|
|
56
49
|
if(opt.inspect){console.log(JSON.stringify({type:"INSPECTED",state,reconciliation}));return}
|
|
57
50
|
if(opt.reconcile){if(!reconciliation){console.log(JSON.stringify({type:"NO_RECONCILIATION"}));return}if(reconciliation.kind==="BLOCKED"){console.log(JSON.stringify({type:"BLOCKED",reason:reconciliation.reason}));return}const ticket=a.tickets?.[reconciliation.subject];if(!ticket)throw new Error(`reconciliation ticket missing: ${reconciliation.subject}`);const ticketPath=join(root,ticket.path),raw=await readFile(ticketPath,"utf8");await writeFile(ticketPath,patchStatus(raw,reconciliation.to),"utf8");for(const file of ["observed-state.json","handoff.json"])try{await writeFile(join(runtime,file),"")}catch{}console.log(JSON.stringify({type:"RECONCILED",reconciliation}));return}
|
|
58
|
-
if(opt.check){const old=await json(join(runtime,"handoff.json"));if(!old){console.log(JSON.stringify({type:"HANDOFF_STALE",reason:"missing handoff"}));return}const candidate=handoff(kernel,integrity,state,a,repo,old.workflow,old.ticket,intent);
|
|
51
|
+
if(opt.check){const old=await json(join(runtime,"handoff.json"));if(!old){console.log(JSON.stringify({type:"HANDOFF_STALE",reason:"missing handoff"}));return}const candidate=handoff(kernel,integrity,state,a,repo,old.workflow,old.ticket,intent);console.log(JSON.stringify(same(old,candidate)?{type:"HANDOFF_FRESH"}:{type:"HANDOFF_STALE",reason:"durable basis changed"}));return}
|
|
59
52
|
if(planned.type!=="ROUTE"){if(planned.type!=="RECONCILE_REQUIRED")try{await writeFile(join(runtime,"handoff.json"),"")}catch{}console.log(JSON.stringify(planned));return}
|
|
60
53
|
if(planned.terminal){console.log(JSON.stringify({type:"TERMINAL",terminal:planned.terminal}));return}if(planned.blocked){console.log(JSON.stringify({type:"BLOCKED",reason:planned.blocked,ticket:planned.ticket??null}));return}
|
|
61
54
|
const wf=kernel.workflows[planned.workflow];if(!wf)throw new Error(`unknown workflow ${planned.workflow}`);if(wf.role==="orchestrator"){console.log(JSON.stringify({type:"ROOT_ACTION",workflow:planned.workflow,ticket:planned.ticket??null}));return}if(!repoAllowed(wf.repository_requirement,repo)){console.log(JSON.stringify({type:"BLOCKED",reason:"PRECONDITION_UNSATISFIED:REPOSITORY_IDENTITY_UNAVAILABLE",workflow:planned.workflow}));return}
|
|
62
|
-
const h=handoff(kernel,integrity,state,a,repo,planned.workflow,planned.ticket??null,intent);
|
|
63
|
-
const fileErrors=await validateDispatchFiles(system,kernel,planned.workflow,h.expertise);if(fileErrors.length)throw Object.assign(new Error(fileErrors.join("; ")),{diagnostic:{classification:"FRAMEWORK_DEFECT_OR_DAMAGED_INSTALLATION",workflow:planned.workflow,source_skill:intent?.source_skill??null,owning_role:h.role,framework_version:integrity.package_version,contract_type:"INSTRUCTION_OR_MODULE_FILE",recommendation:"Update or repair YAAW-SE through the supported installer; preserve project memory."}});
|
|
64
|
-
await mkdir(runtime,{recursive:true});await writeFile(join(runtime,"handoff.json"),JSON.stringify(h,null,2)+"\n");console.log(JSON.stringify({type:"DISPATCH_READY",workflow:planned.workflow,role:h.role,operation:h.operation,ticket:h.ticket,handoff:rel(root,join(runtime,"handoff.json"))}));
|
|
55
|
+
const h=handoff(kernel,integrity,state,a,repo,planned.workflow,planned.ticket??null,intent);await mkdir(runtime,{recursive:true});await writeFile(join(runtime,"handoff.json"),JSON.stringify(h,null,2)+"\n");console.log(JSON.stringify({type:"DISPATCH_READY",workflow:planned.workflow,role:h.role,operation:h.operation,ticket:h.ticket,handoff:rel(root,join(runtime,"handoff.json"))}));
|
|
65
56
|
}
|
|
66
|
-
main().catch(error=>{process.stdout.write(JSON.stringify({type:"FRAMEWORK_STOP",reason:"FRAMEWORK_CONTRACT_INCONSISTENCY",error:String(error?.
|
|
57
|
+
main().catch(error=>{process.stdout.write(JSON.stringify({type:"FRAMEWORK_STOP",reason:"FRAMEWORK_CONTRACT_INCONSISTENCY",error:String(error?.stack??error)})+"\n");process.exitCode=2});
|
|
@@ -76,9 +76,6 @@
|
|
|
76
76
|
"orchestration.recover-interruption": {"role":"orchestrator","operation":"recover-interruption","repository_requirement":"IDENTITY","external_research":"forbidden","execution_context":"ROOT_ONLY"}
|
|
77
77
|
},
|
|
78
78
|
"handoffs": {
|
|
79
|
-
"prd.revise": {"active_artifact":"product","reads":["product"],"writes":["product"],"required_modules":[],"ticket_modules":false,"expected_output":"Human-approved revised product meaning, incremented semantic revision and provenance; READY or unresolved product questions. Downstream staleness is derived by Orchestrator.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","BLOCKED"]},
|
|
80
|
-
"prd.refine": {"active_artifact":"product","reads":["product"],"writes":["product"],"required_modules":[],"ticket_modules":false,"expected_output":"Clarified product text preserving accepted meaning and semantic revision, or a typed product question/stop.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","BLOCKED"]},
|
|
81
|
-
"planning.readiness-review": {"active_artifact":"engineering","reads":["product","engineering","engineering_research","spec","ticket","project_rule","repository"],"writes":["engineering"],"required_modules":[],"ticket_modules":false,"expected_output":"Current-frontier readiness result, source revisions, reason and evidence persisted only in engineering.md.","result_vocabulary":["PASS","MISSING_DECISIONS","PRODUCT_GAP","REPLAN","BLOCKED"]},
|
|
82
79
|
"prd.route": {"active_artifact":"product","reads":["product"],"writes":["product"],"required_modules":[],"ticket_modules":false,"expected_output":"Current product intent or a typed stop.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","BLOCKED"]},
|
|
83
80
|
"planning.route": {"active_artifact":"engineering","reads":["product","engineering","engineering_research","spec","ticket","project_rule","repository"],"writes":["engineering","engineering_research","spec","ticket","project_rule"],"required_modules":[],"ticket_modules":false,"expected_output":"Current engineering frontier readiness or one typed planning result.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","REPLAN","BLOCKED"]},
|
|
84
81
|
"planning.create-spec": {"active_artifact":"engineering","reads":["product","engineering","engineering_research","project_rule","repository"],"writes":["spec"],"required_modules":[],"ticket_modules":false,"expected_output":"One accepted specification or a typed prerequisite/stop.","result_vocabulary":["SUCCESS","PRECONDITION_UNSATISFIED","BLOCKED"]},
|
|
@@ -21,10 +21,14 @@ Partition every material issue into known reusable decisions, one bounded curren
|
|
|
21
21
|
Resolve exactly one current PENDING research artifact whose product/engineering/frontier basis is still current and admitted. Prefer primary sources; record version/date for sensitive facts and conflicts rather than choosing silently. Mark RESOLVED or BLOCKED. Research establishes facts only; return to decision-frontier before promoting anything into an `ENG-*` decision.
|
|
22
22
|
|
|
23
23
|
## Operation: question-round (`planning.question-round`)
|
|
24
|
-
Load only the current frontier and evidence needed for it. Challenge material engineering assumptions, contradictions, failure/data/security/migration/testing/operability gaps, and premature architecture. Eliminate repository-answerable, routine reversible, future-dependent, and prerequisite-blocked questions. Product gaps return to PRD. Do not reopen settled `ENG-*` decisions without new evidence/contradiction/changed upstream intent.
|
|
24
|
+
Load only the current frontier and evidence needed for it. Challenge material engineering assumptions, contradictions, failure/data/security/migration/testing/operability gaps, and premature architecture. Eliminate repository-answerable, routine reversible, future-dependent, and prerequisite-blocked questions. Product gaps return to PRD. Do not reopen settled `ENG-*` decisions without new evidence/contradiction/changed upstream intent.
|
|
25
|
+
|
|
26
|
+
When human input is genuinely required, ask one batch of **1 to 10** material questions; never pad a batch to reach 10. For every user-owned decision or clarification that has two or more credible alternatives, present explicit labeled choices (`A.`, `B.`, `C.`, ...), then a single `Recommended: <label> — <concise reason>` line. The recommendation must state the engineering tradeoff that makes that option preferable within accepted product intent; it is advice, not new product authority. Do not manufacture choices for factual values, genuinely open-ended clarifications, or cases where labeled alternatives would hide the real decision; ask those directly instead. Free-form answers, modified options, combinations, and label-only answers are all valid. If no valid human-owned question remains, do not invent one merely to satisfy the range; return to the frontier/readiness flow.
|
|
27
|
+
|
|
28
|
+
Stop after the batch for human input. Do not start another question round until accepted answers from the current batch have been processed by `planning.record-decisions` and the decision frontier has been recomputed.
|
|
25
29
|
|
|
26
30
|
## Operation: record-decisions (`planning.record-decisions`)
|
|
27
|
-
Validate accepted answers against current product authority and repository evidence. Product meaning gaps return to PRD. Conflicts with accepted `ENG-*` decisions require explicit supersession and invalidation. Otherwise create/update durable decisions with Status, Decision, Reason, material rejected alternatives, implications, and provenance. Increment engineering revision for material contract changes, persist conclusions rather than transcripts, and mark frontier/readiness stale.
|
|
31
|
+
Validate accepted answers against current product authority and repository evidence. Product meaning gaps return to PRD. Conflicts with accepted `ENG-*` decisions require explicit supersession and invalidation. Otherwise create/update durable decisions with Status, Decision, Reason, material rejected alternatives, implications, and provenance. Treat labeled selections as shorthand for the corresponding current-batch alternative, while preserving any free-form modification or combination the human supplied; never persist a bare option label as engineering meaning. If the intended meaning of an answer cannot be determined from the current question batch, do not guess. Increment engineering revision for material contract changes, persist conclusions rather than transcripts, and mark frontier/readiness stale.
|
|
28
32
|
|
|
29
33
|
## Operation: readiness-review (`planning.readiness-review`)
|
|
30
34
|
Freshly decide: could a fresh Implementer execute the next bounded frontier without inventing material product or architecture decisions? Check boundedness, assumptions, blocking research, repository contradiction, ambiguous terminology, failure/state transitions, hidden dependencies, product gaps, and repository identity availability for executable ticket admission. Return `PASS`, `MISSING_DECISIONS`, `PRODUCT_GAP`, `REPLAN`, or `BLOCKED`; persist result, frontier/source revisions, reason, and evidence in `engineering.md` only.
|
|
@@ -15,13 +15,13 @@ Initialize `product.md` from the canonical template only when missing; never ove
|
|
|
15
15
|
Challenge only material contradictions, ambiguous terminology, unsupported assumptions, premature abstractions, important flows/failures/edge cases, and scope/non-goal conflicts. Exclude engineering-only questions and questions whose prerequisites remain unresolved. Ask at most 10 high-leverage questions; options/recommendation are optional and free-form answers are first-class. Stop for human input.
|
|
16
16
|
|
|
17
17
|
## Operation: record-decisions (`prd.record-decisions`)
|
|
18
|
-
Interpret only what the answer supports. Persist conclusions in the relevant product sections, not the question/challenge transcript. Remove settled questions, preserve corrections/non-goals, and increment product revision only when accepted product meaning materially changes. Recompute contradictions/questions and
|
|
18
|
+
Interpret only what the answer supports. Persist conclusions in the relevant product sections, not the question/challenge transcript. Remove settled questions, preserve corrections/non-goals, and increment product revision only when accepted product meaning materially changes. Recompute contradictions/questions and invalidate downstream planning/spec/ticket authority whose product basis became stale.
|
|
19
19
|
|
|
20
20
|
## Operation: revise (`prd.revise`)
|
|
21
|
-
Identify the exact old requirement and requested new meaning. Stress-test impact on behavior, scope, constraints, non-goals, and newly exposed product questions without drifting into engineering design. Update
|
|
21
|
+
Identify the exact old requirement and requested new meaning. Stress-test impact on behavior, scope, constraints, non-goals, and newly exposed product questions without drifting into engineering design. Update `product.md`, increment revision, record provenance, and invalidate downstream dependent contracts. Leave product draft if material questions remain; otherwise run readiness.
|
|
22
22
|
|
|
23
23
|
## Operation: refine (`prd.refine`)
|
|
24
|
-
Improve clarity, organization, deduplication, and completeness without changing accepted semantic meaning or product revision. If the edit changes behavior, scope, constraints, or non-goals—or exposes a genuine semantic contradiction—
|
|
24
|
+
Improve clarity, organization, deduplication, and completeness without changing accepted semantic meaning or product revision. If the edit changes behavior, scope, constraints, or non-goals—or exposes a genuine semantic contradiction—switch to revision instead of silently changing meaning.
|
|
25
25
|
|
|
26
26
|
## Operation: readiness (`prd.readiness`)
|
|
27
27
|
Ask whether a fresh Planner can understand goal, users, expected behavior, scope, constraints, non-goals, and remaining product unknowns without the original chat, and whether any material current-frontier ambiguity would force invented product intent. Return `READY`, `NEEDS_QUESTIONS`, or `BLOCKED`. On READY, persist `status: ready` in `product.md`. Future fog is allowed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaaw-se",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14-dev.39042534464.1",
|
|
4
4
|
"description": "Artifact-first autonomous software engineering workflow installer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,10 +28,7 @@
|
|
|
28
28
|
"prepack": "npm run test && npm run build",
|
|
29
29
|
"smoke:tarball": "npm run build && node scripts/smoke_npm_tarball.mjs",
|
|
30
30
|
"smoke:update": "npm run build && node scripts/smoke_npm_update.mjs",
|
|
31
|
-
"validate:schemas": "node scripts/validate_schemas.mjs"
|
|
32
|
-
"validate:contracts": "node scripts/validate_contracts.mjs .yaaw-core/system skills",
|
|
33
|
-
"smoke:dispatch-update": "npm run build && node scripts/smoke_dispatch_update.mjs",
|
|
34
|
-
"audit:workflows": "node scripts/audit_workflow_contracts.mjs .yaaw-core/system skills"
|
|
31
|
+
"validate:schemas": "node scripts/validate_schemas.mjs"
|
|
35
32
|
},
|
|
36
33
|
"dependencies": {
|
|
37
34
|
"@clack/prompts": "1.8.1",
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { readFile, lstat, realpath } from 'node:fs/promises';
|
|
2
|
-
import { resolve, relative, isAbsolute, join } from 'node:path';
|
|
3
|
-
// The router uses these values at each branch; there is no separate target inventory.
|
|
4
|
-
export const ROUTES = Object.freeze({product:'prd.route',planning:'planning.route',spec:'planning.create-spec',tickets:'planning.create-tickets',replan:'planning.replan',repair:'implementation.repair-ticket',review:'review.review-ticket',verify:'implementation.verify-ticket',implement:'implementation.implement-ticket'});
|
|
5
|
-
export const ROUTER_DISPATCH_TARGETS = Object.freeze([...new Set(Object.values(ROUTES))]);
|
|
6
|
-
// Typed worker outcomes consumed by the host continuation/lifecycle protocol.
|
|
7
|
-
const RESULTS = {
|
|
8
|
-
'prd.route':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
9
|
-
'prd.revise':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
10
|
-
'prd.refine':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
11
|
-
'planning.route':['READY','HUMAN_INPUT_REQUIRED','REPLAN','BLOCKED'],
|
|
12
|
-
'planning.readiness-review':['PASS','MISSING_DECISIONS','PRODUCT_GAP','REPLAN','BLOCKED'],
|
|
13
|
-
'planning.create-spec':['SUCCESS','PRECONDITION_UNSATISFIED','BLOCKED'],
|
|
14
|
-
'planning.create-tickets':['SUCCESS','PRECONDITION_UNSATISFIED','BLOCKED'],
|
|
15
|
-
'planning.replan':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
16
|
-
'implementation.implement-ticket':['VERIFICATION_PASS','VERIFICATION_FAIL','REPLAN_REQUIRED','PRECONDITION_UNSATISFIED','BLOCKED'],
|
|
17
|
-
'implementation.verify-ticket':['VERIFICATION_PASS','VERIFICATION_FAIL','IMPLEMENTATION_INCOMPLETE','REPLAN_REQUIRED','BLOCKED'],
|
|
18
|
-
'implementation.repair-ticket':['VERIFICATION_PASS','VERIFICATION_FAIL','REPLAN_REQUIRED','BLOCKED'],
|
|
19
|
-
'review.review-ticket':['PASS','REPAIR','REPLAN','BLOCKED']
|
|
20
|
-
};
|
|
21
|
-
// A shortcut's completion must describe the operation it actually requests.
|
|
22
|
-
const INTENTS = {
|
|
23
|
-
CONTINUE:['orchestration.route','CONTINUE_UNTIL_STOP'],
|
|
24
|
-
CONTINUE_PRODUCT:['prd.route','PRODUCT_READY'],
|
|
25
|
-
REVISE_PRODUCT:['prd.revise','PRODUCT_REVISION_ADVANCED'],
|
|
26
|
-
REFINE_PRODUCT:['prd.refine','PRODUCT_REFINED'],
|
|
27
|
-
CONTINUE_PLANNING:['planning.route','PLANNING_READY'],
|
|
28
|
-
PLANNING_REVIEW:['planning.readiness-review','PLANNING_READINESS_DECIDED'],
|
|
29
|
-
CREATE_SPEC:['planning.create-spec','CURRENT_SPEC_ACCEPTED'],
|
|
30
|
-
CREATE_TICKETS:['planning.create-tickets','CURRENT_TICKETS_REGISTERED'],
|
|
31
|
-
IMPLEMENT:['implementation.implement-ticket','TICKET_REVIEW_REQUIRED'],
|
|
32
|
-
REPAIR:['implementation.repair-ticket','TICKET_REVIEW_REQUIRED'],
|
|
33
|
-
REVIEW:['review.review-ticket','TICKET_REVIEW_APPLIED']
|
|
34
|
-
};
|
|
35
|
-
export function dispatchableWorkflows(kernel) {
|
|
36
|
-
return new Set([...ROUTER_DISPATCH_TARGETS,...Object.values(kernel.skills??{}).filter(s=>s.role!=='orchestrator').map(s=>s.requested_workflow),...Object.keys(kernel.handoffs??{})]);
|
|
37
|
-
}
|
|
38
|
-
const managedPath=p=>typeof p==='string'&&p.startsWith('.yaaw-core/system/')&&!p.includes('\\')&&!p.split('/').includes('..')&&p.endsWith('.md');
|
|
39
|
-
async function managedFile(system,path) {
|
|
40
|
-
if(!managedPath(path))throw new Error(`invalid managed system reference ${path}`);
|
|
41
|
-
const target=resolve(system,path.slice('.yaaw-core/system/'.length)),root=await realpath(system),actual=await realpath(target),rel=relative(root,actual);
|
|
42
|
-
if(rel.startsWith('..')||isAbsolute(rel)||!(await lstat(target)).isFile())throw new Error(`invalid managed system file ${path}`);
|
|
43
|
-
return target;
|
|
44
|
-
}
|
|
45
|
-
const operationHeader=(id,w)=>`## Operation: ${w.operation} (\`${id}\`)`;
|
|
46
|
-
export async function validateDispatchFiles(system,k,workflow,expertise=[]) {
|
|
47
|
-
const paths=[k.roles[k.workflows[workflow]?.role]?.document,...expertise],errors=[];
|
|
48
|
-
for(const path of paths)try{await managedFile(system,path)}catch{errors.push(`${workflow}: missing/invalid dispatch file ${path}`)}
|
|
49
|
-
const w=k.workflows[workflow];
|
|
50
|
-
try{const text=await readFile(await managedFile(system,k.roles[w.role].document),"utf8");if(!text.includes(operationHeader(workflow,w)))errors.push(`${workflow}: missing selected role operation`)}catch{}
|
|
51
|
-
return errors;
|
|
52
|
-
}
|
|
53
|
-
const stringList=x=>Array.isArray(x)&&x.every(v=>typeof v==='string'&&v.length>0)&&new Set(x).size===x.length;
|
|
54
|
-
const sameSet=(a,b)=>stringList(a)&&stringList(b)&&a.length===b.length&&a.every(x=>b.includes(x));
|
|
55
|
-
export function validateContracts(k) {
|
|
56
|
-
const errors=[],roles=k.roles??{},workflows=k.workflows??{},artifacts=k.artifacts??{},modules=k.modules??{},handoffs=k.handoffs??{};
|
|
57
|
-
const error=(id,message)=>errors.push(`${id}: ${message}`);
|
|
58
|
-
for(const [id,a] of Object.entries(artifacts)) {
|
|
59
|
-
if(!roles[a.owner]&&!['lifecycle','installer','none'].includes(a.owner))error(id,'unknown artifact owner');
|
|
60
|
-
if(a.embedded_in&&!artifacts[a.embedded_in])error(id,'unknown embedded artifact');
|
|
61
|
-
if(!a.path&&!a.path_pattern&&!a.embedded_in&&!a.virtual)error(id,'artifact has no locator');
|
|
62
|
-
}
|
|
63
|
-
for(const [id,r] of Object.entries(roles)){
|
|
64
|
-
if(!managedPath(r.document))error(id,'invalid role document reference');
|
|
65
|
-
for(const kind of ['reads','writes']) {
|
|
66
|
-
if(!stringList(r[kind]))error(id,`invalid role ${kind}`);
|
|
67
|
-
else for(const a of r[kind])if(!artifacts[a])error(id,`unknown ${kind} artifact ${a}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
for(const [id,w] of Object.entries(workflows)) {
|
|
71
|
-
if(!roles[w.role])error(id,`unknown owning role ${w.role}`);
|
|
72
|
-
if(typeof w.operation!=='string'||!w.operation)error(id,'missing operation');
|
|
73
|
-
if(!['NONE','INSPECT','IDENTITY'].includes(w.repository_requirement))error(id,'invalid repository requirement');
|
|
74
|
-
}
|
|
75
|
-
for(const [id,s] of Object.entries(k.skills??{})) {
|
|
76
|
-
const w=workflows[s.requested_workflow];
|
|
77
|
-
if(!w)error(id,`unknown requested workflow ${s.requested_workflow}`);
|
|
78
|
-
else if(w.role!==s.role)error(id,'skill role differs from workflow owner');
|
|
79
|
-
if(!roles[s.role])error(id,'unknown skill role');
|
|
80
|
-
const intent=INTENTS[s.desired_outcome];
|
|
81
|
-
if(!intent||s.requested_workflow!==intent[0]||s.completion_kind!==intent[1])error(id,'shortcut intent/workflow/completion semantics disagree');
|
|
82
|
-
}
|
|
83
|
-
for(const id of dispatchableWorkflows(k)) {
|
|
84
|
-
const w=workflows[id];
|
|
85
|
-
if(!w)error(id,'dispatch target has no workflow');
|
|
86
|
-
if(w?.role==='orchestrator')error(id,'root operation cannot be a worker dispatch target');
|
|
87
|
-
if(!handoffs[id])error(id,'dispatch target has no handoff contract');
|
|
88
|
-
}
|
|
89
|
-
for(const [id,h] of Object.entries(handoffs)) {
|
|
90
|
-
const w=workflows[id],r=roles[w?.role];
|
|
91
|
-
if(!w||!r){error(id,'handoff has no workflow/owning role');continue}
|
|
92
|
-
for(const kind of ['reads','writes']) {
|
|
93
|
-
if(!stringList(h[kind])){error(id,`invalid handoff ${kind}`);continue}
|
|
94
|
-
for(const a of h[kind]) {
|
|
95
|
-
if(!artifacts[a])error(id,`unknown ${kind} artifact ${a}`);
|
|
96
|
-
if(!r[kind]?.includes(a))error(id,`${kind} outside ${w.role} authority: ${a}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if(!artifacts[h.active_artifact])error(id,`unknown active artifact ${h.active_artifact}`);
|
|
100
|
-
else if(!h.reads?.includes(h.active_artifact)&&!h.writes?.includes(h.active_artifact))error(id,'active artifact outside handoff I/O');
|
|
101
|
-
if(!stringList(h.required_modules))error(id,'invalid required modules');
|
|
102
|
-
else for(const m of h.required_modules)if(!modules[m]?.usable_by?.includes(w.role))error(id,`missing/unauthorized module ${m}`);
|
|
103
|
-
if(typeof h.ticket_modules!=='boolean')error(id,'invalid ticket_modules');
|
|
104
|
-
if(typeof h.expected_output!=='string'||!h.expected_output.trim())error(id,'missing expected output');
|
|
105
|
-
if(!sameSet(h.result_vocabulary,RESULTS[id]))error(id,'result vocabulary incompatible with worker protocol');
|
|
106
|
-
}
|
|
107
|
-
for(const [id,m] of Object.entries(modules)) {
|
|
108
|
-
if(!managedPath(m.path))error(id,'invalid module path reference');
|
|
109
|
-
if(!stringList(m.usable_by)||m.usable_by.some(r=>!roles[r]))error(id,'unknown module role');
|
|
110
|
-
}
|
|
111
|
-
for(const row of k.lifecycle?.legal??[]) {
|
|
112
|
-
if(!workflows[row.workflow]||!roles[row.owner])error('lifecycle','unresolved workflow/owner');
|
|
113
|
-
if(row.writer&&!roles[row.writer])error('lifecycle','unknown writer');
|
|
114
|
-
}
|
|
115
|
-
const routing=k.lifecycle?.routing??{};
|
|
116
|
-
for(const [name,value] of Object.entries(routing)) {
|
|
117
|
-
if(name.endsWith('_workflow')&&!ROUTER_DISPATCH_TARGETS.includes(value))error(name,'routing workflow is not a router target');
|
|
118
|
-
}
|
|
119
|
-
for(const row of Object.values(routing.invalidation_routes??{}))if(!ROUTER_DISPATCH_TARGETS.includes(row.workflow))error('invalidation_routes','unknown router target');
|
|
120
|
-
return errors;
|
|
121
|
-
}
|
|
122
|
-
export async function validateSystemContracts(system,skills=null) {
|
|
123
|
-
const k=JSON.parse(await readFile(join(system,'kernel.yaml'),'utf8')),errors=validateContracts(k);
|
|
124
|
-
async function textAt(path,id) {
|
|
125
|
-
try{return await readFile(await managedFile(system,path),'utf8')}catch{errors.push(`${id}: missing/invalid referenced file ${path}`);return ''}
|
|
126
|
-
}
|
|
127
|
-
for(const [id,r] of Object.entries(k.roles??{})) {
|
|
128
|
-
const text=await textAt(r.document,id);
|
|
129
|
-
for(const [wid,w] of Object.entries(k.workflows??{}))if(w.role===id&&!text.includes(operationHeader(wid,w)))errors.push(`${wid}: missing role operation`);
|
|
130
|
-
}
|
|
131
|
-
for(const [id,m] of Object.entries(k.modules??{}))await textAt(m.path,id);
|
|
132
|
-
if(skills) {
|
|
133
|
-
const {readdir}=await import('node:fs/promises');
|
|
134
|
-
const dirs=(await readdir(skills,{withFileTypes:true})).filter(e=>e.isDirectory()).map(e=>e.name);
|
|
135
|
-
if(!sameSet(dirs,Object.keys(k.skills??{})))errors.push('public skill directory/catalog mismatch');
|
|
136
|
-
for(const [id,s] of Object.entries(k.skills??{})) {
|
|
137
|
-
try {
|
|
138
|
-
const text=await readFile(join(skills,id,'SKILL.md'),'utf8');
|
|
139
|
-
for(const marker of [`name: ${id}`,`ROLE: \`${s.role}\``,`WORKFLOW: \`${s.requested_workflow}\``,`INTENT: \`${s.desired_outcome}\``, 'system/engine/runtime.mjs',`--invoke-skill ${id}`])if(!text.includes(marker))errors.push(`${id}: skill metadata/runtime invocation mismatch (${marker})`);
|
|
140
|
-
}catch{errors.push(`${id}: missing public skill`)}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
// Completion and intent enums are already machine contracts; do not duplicate them.
|
|
144
|
-
const schema=JSON.parse(await readFile(join(system,'schemas/intent.schema.json'),'utf8'));
|
|
145
|
-
for(const [id,s] of Object.entries(k.skills??{}))for(const field of ['desired_outcome','completion_kind'])if(!schema.properties[field].enum.includes(s[field]))errors.push(`${id}: invalid ${field}`);
|
|
146
|
-
return errors;
|
|
147
|
-
}
|