Repository navigation
Expand file tree
/
Copy pathqdl-v1-offspring.js
More file actions
71 lines (71 loc) · 14.8 KB
/
Copy pathqdl-v1-offspring.js
File metadata and controls
71 lines (71 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
(function(root){
'use strict';
const node=typeof module!=='undefined'&&module.exports;
const V=node?require('./qdl-v1.js'):root.QDLV1,T=node?require('./qdl-v1-types.js'):root.QDLV1Types,C=node?require('./qdl-v1-contract.js'):root.QDLV1Contract;
const Q=node?require('./core.js'):root.Quinelings,A=node?require('./anatomy.js'):root.Anatomy,D=node?require('./qdl.js'):root.QDL;
const Crypto=node?require('./ranch-crypto.js'):root.RanchCrypto;
const POLICY='qdl-v1-collaboration-experimental-1',TRAITS=['elongation','spread','curvature','gestureGain','tempo','pigmentGain'];
const copy=x=>JSON.parse(JSON.stringify(x)),canon=Q.canon,hash=x=>Crypto.sha256(x),bytes=x=>new TextEncoder().encode(typeof x==='string'?x:JSON.stringify(x)).length;
function fail(code,path,message){const e=new Error(message);e.code=code;e.path=path;throw e;}
function check(ok,code,path,message){if(!ok)fail(code,path,message);}
function closed(x,required,optional=[],path='$'){check(x&&typeof x==='object'&&!Array.isArray(x),'invalid-input',path,'Expected record');for(const k of required)check(Object.hasOwn(x,k),'invalid-input',path+'.'+k,'Missing field');for(const k of Object.keys(x))check(required.includes(k)||optional.includes(k),'invalid-input',path+'.'+k,'Unknown field');}
function identifier(x,path){check(typeof x==='string'&&/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(x)&&!['__proto__','constructor','prototype'].includes(x),'identifier',path,'Expected a safe 1–64 character identifier');}
function recipe(input){T.finiteJSON(input);check(input&&typeof input==='object','invalid-input','$.recipe','Expected recipe');const fields={compose:['donorOutput','recipientInput'],mate:['donorNode','replaceNode'],merge:[],body:['base']};check(Object.hasOwn(fields,input.kind),'invalid-input','$.recipe.kind','Unknown recipe');closed(input,['kind',...fields[input.kind]],[],'$.recipe');for(const k of fields[input.kind])if(k==='base')check(input[k]===0||input[k]===1,'invalid-input','$.recipe.base','Expected ordered base 0 or 1');else identifier(input[k],'$.recipe.'+k);return copy(input);}
function parents(input){check(Array.isArray(input)&&Object.getPrototypeOf(input)===Array.prototype&&input.length===2&&Reflect.ownKeys(input).length===3&&['0','1'].every(k=>{const d=Object.getOwnPropertyDescriptor(input,k);return d&&Object.hasOwn(d,'value')&&d.enumerable;}),'invalid-input','$.parents','Expected two inert ordered canonical v1 sources');return input.map((p,i)=>{try{return V.admit(p);}catch(e){if(e.code==='unsupported-registry')fail('profile-mismatch','$.parents.'+i,e.message);fail('profile-mismatch','$.parents.'+i,'Parent must be a canonical admitted v1 source: '+e.message);}});}
function topo(task){const done=new Set(),pending=task.nodes.slice(),out=[];while(pending.length){const i=pending.findIndex(n=>n.inputs.every(x=>done.has(x)));check(i>=0,'cycle','$.task','Unresolved dependency');const n=pending.splice(i,1)[0];done.add(n.id);out.push(n);}return out;}
function closure(task,roots){const nodes=new Map(task.nodes.map(n=>[n.id,n])),keep=new Set();function visit(id){check(nodes.has(id),'selector','$.recipe','Unknown selected node '+id);if(keep.has(id))return;keep.add(id);nodes.get(id).inputs.forEach(visit);}roots.forEach(visit);return keep;}
function guarded(task){const keep=new Set();for(const n of task.nodes)if(n.op==='action'){keep.add(n.id);for(const id of closure(task,[n.inputs[0]]))keep.add(id);}return keep;}
function sourcePin(parent){return {sourceHash:parent.sourceHash,registry:parent.payload.registry,registryDigest:parent.payload.registryDigest,canonical:parent.payload.canonical};}
function assemble(ps,r){
check(ps[0].payload.registry===ps[1].payload.registry&&ps[0].payload.registryDigest===ps[1].payload.registryDigest&&ps[0].payload.canonical===ps[1].payload.canonical,'profile-mismatch','$.parents','Parents use different profiles');
const tasks=ps.map(p=>p.payload.task),mappings=[],portMappings=[],guardNodes=[...guarded(tasks[1])];let task,seam=null;
if(r.kind==='body'){const base=tasks[r.base];task=copy(base);for(const n of base.nodes)mappings.push({parent:r.base,parentNodeId:n.id,nodeId:n.id});for(const p of ps[r.base].ports)portMappings.push({parent:r.base,parentName:p.name,name:p.name,nodeId:p.nodeId});return {task,nodeMappings:mappings,portMappings,guardNodes:[...guarded(base)],seam,ports:copy(ps[r.base].ports),diagnostics:[]};}
check(ps[0].sourceHash!==ps[1].sourceHash,'same-source','$.parents','Graph-changing recipes require distinct exact parent sources');
const ordered=tasks.map(topo),maps=ordered.map((ns,role)=>new Map(ns.map((n,i)=>[n.id,'p'+role+'n'+i])));
function imported(role,keep){return ordered[role].filter(n=>!keep||keep.has(n.id)).map(n=>{const id=maps[role].get(n.id),out={...copy(n),id,inputs:n.inputs.map(k=>maps[role].get(k))};mappings.push({parent:role,parentNodeId:n.id,nodeId:id});if(n.op==='input'){const name='p'+role+'_'+n.params.name;check(Array.from(name).length<=64&&!name.includes('.')&&!['__proto__','constructor','prototype'].includes(name),'identifier','$.ports.'+name,'Namespaced port exceeds the safe 64-scalar own-name boundary');out.params={name};portMappings.push({parent:role,parentName:n.params.name,name,nodeId:id});}return out;});}
let nodes,outputs;
if(r.kind==='merge'){
check(tasks[0].outputs.length+tasks[1].outputs.length<=16,'ports','$.recipe','Merge exceeds 16 report inputs');nodes=[...imported(0),...imported(1)];const input=tasks.flatMap((t,i)=>t.outputs.map(k=>maps[i].get(k))),labels=tasks.flatMap((t,i)=>t.outputs.map((_,j)=>(i?'b':'a')+j)),types=new Map(nodes.map(n=>[n.id,n.type]));
nodes.push({id:'joined',op:'report',inputs:input,params:{labels},type:C.inferNode({id:'joined',op:'report',inputs:input,params:{labels}},input.map(k=>types.get(k)))});mappings.push({parent:'generated',nodeId:'joined'});outputs=['joined'];
}else{
const donor=r.kind==='compose'?r.donorOutput:r.donorNode,target=r.kind==='compose'?r.recipientInput:r.replaceNode,donorNode=tasks[0].nodes.find(n=>n.id===donor),recipient=tasks[1].nodes.find(n=>n.id===target);
check(donorNode&&recipient,'selector','$.recipe','Unknown selected donor or recipient');
if(r.kind==='compose'){check(tasks[0].outputs.includes(donor),'selector','$.recipe.donorOutput','Compose donor must be an exported output');check(recipient.op==='input','selector','$.recipe.recipientInput','Compose replaces a runtime input only');}else check(recipient.op==='literal','selector','$.recipe.replaceNode','Mate replaces an immutable literal only');
check(!guardNodes.includes(target),'protected-guard','$.recipe','Recipient actions and entire guard ancestor cones are protected');
const keep=closure(tasks[0],[donor]);check(tasks[0].nodes.filter(n=>keep.has(n.id)).every(n=>n.op!=='action'),'impure-donor','$.recipe','Donor predecessor closure must be action-free');
check(T.canonical(T.normalize(donorNode.type))===T.canonical(T.normalize(recipient.type)),'unit-type','$.recipe','Seam requires exact normalized full type and refinement equality');
const donorId=maps[0].get(donor),targetId=maps[1].get(target);nodes=[...imported(0,keep),...imported(1)].filter(n=>n.id!==targetId).map(n=>({...n,inputs:n.inputs.map(k=>k===targetId?donorId:k)}));outputs=tasks[1].outputs.map(k=>k===target?donorId:maps[1].get(k));const live=closure({nodes},outputs);nodes=nodes.filter(n=>live.has(n.id));
for(const id of guardNodes){const original=tasks[1].nodes.find(n=>n.id===id),expected={...copy(original),id:maps[1].get(id),inputs:original.inputs.map(k=>maps[1].get(k))};if(original.op==='input')expected.params={name:'p1_'+original.params.name};check(nodes.some(n=>original.op==='action'?n.id===expected.id&&n.op==='action'&&canon(n.type)===canon(expected.type)&&canon(n.params)===canon(expected.params)&&n.inputs[0]===expected.inputs[0]:canon(n)===canon(expected)),'protected-guard','$.recipe','Recipient action/guard cone changed or was pruned');}
const downstream=new Set([donorId]);for(const n of topo({nodes}))if(n.inputs.some(k=>downstream.has(k)))downstream.add(n.id);
const integration=nodes.find(n=>n.id.startsWith('p1n')&&!['input','literal'].includes(n.op)&&downstream.has(n.id));check(integration,'disconnected-integration','$.recipe','Donor must feed surviving original recipient computation and output; terminal swaps refuse');
seam={donorNode:donorId,recipientNode:targetId,type:copy(donorNode.type),integrationNode:integration.id};
}
check(nodes.length<=64,'node-budget','$.task','Child exceeds 64 nodes');task=C.validate({format:'qdl-task',version:1,nodes:topo({nodes}),outputs}).task;
const live=new Set(task.nodes.map(n=>n.id));return {task,nodeMappings:mappings.filter(m=>live.has(m.nodeId)),portMappings:portMappings.filter(m=>live.has(m.nodeId)),guardNodes:guardNodes.map(k=>maps[1].get(k)).filter(k=>live.has(k)),seam,ports:C.validate(task).ports,diagnostics:[{code:'repeats-reset',path:'$.recipe',message:'Graph-changing recipes use one occurrence; merge is a parallel report, not causal integration.'}]};
}
function compatibility(parentSources,selectedRecipe){try{const r=recipe(selectedRecipe),ps=parents(parentSources),out=assemble(ps,r);return {status:'compatible',parents:ps.map(sourcePin),recipe:r,...copy(out)};}catch(e){return {status:'incompatible',diagnostics:[{code:e.code||'offspring-refused',path:e.path||'$',message:String(e.message||e).slice(0,512)}]};}}
const clamp=(x,a,b)=>Math.max(a,Math.min(b,x)),quant=x=>Math.round(x*1e6)/1e6||0;
function phenotype(ps,task,r,seed,nonce){
const parentTraits=ps.map(p=>p.payload.design.heredity?{basis:'authored-source-traits',traits:copy(p.payload.design.heredity.traits)}:{basis:'source-seeded-traits',traits:Object.fromEntries(TRAITS.map(k=>[k,parseInt(hash(p.sourceHash+'\n'+k).slice(0,8),16)%2001-1000]))});
const traits={},draws=[];for(const k of TRAITS){const pick=parseInt(hash(seed+'\n'+k).slice(0,8),16)%3,a=parentTraits[0].traits[k],b=parentTraits[1].traits[k];traits[k]=pick===0?a:pick===1?b:Math.floor((a+b)/2);draws.push({trait:k,selection:['parent0','parent1','floor-midpoint'][pick],value:traits[k]});}
const base=r.kind==='body'?r.base:1,design=D.create(ps[base].payload.design.family),generated=A.generateV1(task,parseInt(seed.slice(0,8),16));design.anatomy=generated.anatomy;design.motion.gesture=generated.gesture;
for(const part of design.anatomy.components)if(part.kind==='spine'){part.length=quant(clamp(part.length*(1+.12*traits.elongation/1000),.12,1.2));part.radii=part.radii.map(v=>quant(clamp(v*(1+.10*traits.spread/1000),.015,.16)));part.bend[0]=quant(clamp(part.bend[0]+.025*traits.curvature/1000,-.2,.2));}else{part.axes[1]=quant(clamp(part.axes[1]*(1+.12*traits.elongation/1000),.04,.35));for(const i of [0,2])part.axes[i]=quant(clamp(part.axes[i]*(1+.10*traits.spread/1000),.04,.35));}
design.motion.gesture.strength=quant(.65+.10*traits.gestureGain/1000);design.motion.phaseRate=quant(.038+.007*traits.tempo/1000);design.chroma.strength=quant(.85+.10*traits.pigmentGain/1000);
design.heredity={model:'bounded-traits-experimental',parents:ps.map(p=>p.sourceHash.slice(3)),seedDigest:seed,nonce,traits};D.validateBindings(design,task);A.validateOwners(design.anatomy,task.nodes);A.validateGesture(design.motion.gesture);return {design,evidence:{parents:parentTraits,traits,draws}};
}
function projection(task,repeats){const ordered=topo(task),ids=new Map(ordered.map((n,i)=>[n.id,i]));return {repeats,nodes:ordered.map(n=>({op:n.op,type:n.type,params:n.op==='input'?{port:ids.get(n.id)}:n.params,inputs:n.inputs.map(k=>ids.get(k))})),outputs:task.outputs.map(k=>ids.get(k))};}
function bodyProjection(design,task){const result=copy(design),ids=new Map(topo(task).map((n,i)=>[n.id,i]));delete result.heredity;if(result.anatomy){delete result.anatomy.seed;delete result.anatomy.compiler;for(const o of result.anatomy.owners)o.node=ids.get(o.node);}return result;}
function build(parentSources,options){
T.finiteJSON(options);closed(options,['recipe','nonce','name'],['thought']);check(Number.isInteger(options.nonce)&&options.nonce>=0&&options.nonce<=4294967295,'invalid-input','$.nonce','Nonce must be uint32');check(typeof options.name==='string'&&options.name.length>0&&Array.from(options.name).length<=120,'invalid-input','$.name','Name must be 1–120 Unicode scalar values');
const r=recipe(options.recipe),ps=parents(parentSources),assembled=assemble(ps,r),base=ps[r.kind==='body'?r.base:1];let thought;
if(r.kind==='body'){check(!Object.hasOwn(options,'thought'),'invalid-input','$.thought','Body preserves the base declaration; omit thought');thought=copy(base.payload.thought);}else{check(Object.hasOwn(options,'thought')&&options.thought&&typeof options.thought==='object'&&!Array.isArray(options.thought),'missing-declaration','$.thought','Task-changing construction requires a complete authored six-array declaration');thought=copy(options.thought);}
V.validateThought(thought,assembled.task);
const construction={parents:ps.map(sourcePin),recipe:r,nonce:options.nonce,name:options.name,...(r.kind==='body'?{}:{thought:copy(thought)})},key={policy:POLICY,...construction,thought},candidateId='qc_'+hash(canon(key)),seedDigest=hash(POLICY+'\n'+canon(key)),inherited=phenotype(ps,assembled.task,r,seedDigest,options.nonce),repeats=r.kind==='body'?base.payload.repeats:1;
let program;try{program=V.build({name:options.name,thought,task:assembled.task,design:inherited.design,repeats});}catch(e){if(!e.code&&/quine source exceeds/i.test(e.message))fail('source-budget','$.child.source',e.message);throw e;}const child=V.admit(program);check(bytes(child.source)<=65536,'source-budget','$.child.source','Child exceeds 64 KiB');
const changes={sourceChanged:child.source!==base.source,taskSyntaxChanged:canon(projection(assembled.task,repeats))!==canon(projection(base.payload.task,base.payload.repeats)),bodyChanged:canon(bodyProjection(child.payload.design,assembled.task))!==canon(bodyProjection(base.payload.design,base.payload.task))};check(changes.taskSyntaxChanged||changes.bodyChanged,'no-novelty','$.recipe','Renaming and ancestry assertions alone are not a new task or body');
const classification=r.kind==='merge'?'parallel-report':r.kind==='body'?'body-only':'causal-integration',childSourceHash=child.sourceHash,derivationId='qd_'+hash(canon({policy:POLICY,candidateId,childSourceHash,construction}));
const lineage={format:'qdl-v1-derivation-experimental',id:derivationId,policy:POLICY,candidateId,childSourceHash,construction,classification,changes:copy(changes),nodeMappings:assembled.nodeMappings,portMappings:assembled.portMappings,seam:assembled.seam,heredity:inherited.evidence,evidence:'replayed-construction'};
check(bytes(lineage)<=32768,'lineage-budget','$.lineage','Derivation exceeds 32 KiB');const candidate={format:'qdl-v1-candidate-experimental',candidateId,derivationId,childSourceHash,child,classification,changes,diagnostics:assembled.diagnostics,lineage};check(bytes(candidate)<=2097152,'candidate-budget','$','Candidate exceeds 2 MiB');return copy(candidate);
}
const api={POLICY,compatibility,build};if(node)module.exports=api;root.QDLV1Offspring=api;
})(typeof globalThis!=='undefined'?globalThis:this);