mecord-connect 1.0.1 → 2.0.0

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.
Files changed (61) hide show
  1. package/package.json +1 -1
  2. package/runtime/app/apps/local-agent/src/control-center.js +61 -0
  3. package/runtime/app/apps/local-agent/src/main.js +142 -14
  4. package/runtime/app/apps/local-agent/src/migration-proofs.js +194 -0
  5. package/runtime/app/apps/local-agent/src/relay-agent.js +216 -8
  6. package/runtime/app/apps/local-agent/src/runtime-factory.js +16 -4
  7. package/runtime/app/apps/local-agent/src/server.js +1353 -21
  8. package/runtime/app/src/capabilities/filesystem.js +284 -2
  9. package/runtime/app/src/capabilities/index.js +2 -0
  10. package/runtime/app/src/capabilities/perception.js +138 -0
  11. package/runtime/app/src/capabilities/process.js +369 -2
  12. package/runtime/app/src/capabilities/sandboxed-compute.js +226 -0
  13. package/runtime/app/src/capabilities/windows-uia.js +295 -10
  14. package/runtime/app/src/core/audit.js +88 -0
  15. package/runtime/app/src/core/authority-kernel.js +209 -0
  16. package/runtime/app/src/core/capability-policy.js +12 -1
  17. package/runtime/app/src/core/capability-sdk.js +188 -0
  18. package/runtime/app/src/core/conversation-ledger.js +146 -0
  19. package/runtime/app/src/core/desired-state-reconciler.js +90 -0
  20. package/runtime/app/src/core/desired-state.js +470 -0
  21. package/runtime/app/src/core/device-pool.js +318 -0
  22. package/runtime/app/src/core/device-routing.js +31 -6
  23. package/runtime/app/src/core/digital-operations.js +707 -0
  24. package/runtime/app/src/core/durable-state.js +20 -5
  25. package/runtime/app/src/core/enterprise-policy.js +290 -0
  26. package/runtime/app/src/core/evaluation.js +336 -0
  27. package/runtime/app/src/core/event-runtime.js +267 -0
  28. package/runtime/app/src/core/event-ticker.js +66 -0
  29. package/runtime/app/src/core/execution-optimizer.js +255 -0
  30. package/runtime/app/src/core/index.js +8 -0
  31. package/runtime/app/src/core/intent-kernel.js +152 -0
  32. package/runtime/app/src/core/machine-state.js +2 -1
  33. package/runtime/app/src/core/organization-coordinator.js +410 -0
  34. package/runtime/app/src/core/outcome-planner.js +179 -0
  35. package/runtime/app/src/core/perception-graph.js +365 -0
  36. package/runtime/app/src/core/perception-publication.js +139 -0
  37. package/runtime/app/src/core/policy.js +10 -1
  38. package/runtime/app/src/core/procedure-memory.js +399 -0
  39. package/runtime/app/src/core/product-identity.js +4 -1
  40. package/runtime/app/src/core/relay-client.js +64 -3
  41. package/runtime/app/src/core/resilience-matrix.js +176 -0
  42. package/runtime/app/src/core/resilience-simulator.js +156 -0
  43. package/runtime/app/src/core/resource-identity.js +55 -0
  44. package/runtime/app/src/core/resource-leases.js +249 -0
  45. package/runtime/app/src/core/runtime.js +13 -11
  46. package/runtime/app/src/core/semantic-checkpoint.js +420 -0
  47. package/runtime/app/src/core/side-effect.js +30 -0
  48. package/runtime/app/src/core/studio-executor.js +527 -0
  49. package/runtime/app/src/core/studio-teach.js +494 -0
  50. package/runtime/app/src/core/task-failure.js +61 -0
  51. package/runtime/app/src/core/task-orchestrator.js +528 -21
  52. package/runtime/app/src/core/task-store.js +10 -1
  53. package/runtime/app/src/core/task-verifier.js +154 -0
  54. package/runtime/app/src/core/task.js +38 -5
  55. package/runtime/app/src/core/team-coordinator.js +988 -0
  56. package/runtime/app/src/core/types.js +18 -0
  57. package/runtime/app/src/core/verification-kernel.js +82 -0
  58. package/runtime/app/src/core/world-model.js +528 -0
  59. package/runtime/native/operator-windows-dpapi.exe +0 -0
  60. package/runtime/native/operator-windows-uia.exe +0 -0
  61. package/runtime/runtime-manifest.json +224 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mecord-connect",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "Secure one-command Mecord Connect runtime for connecting ChatGPT to a user-authorized Windows computer.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,61 @@
1
+ export function renderControlCenter(nonce ) {
2
+ const safeNonce = nonce.replace(/[^A-Za-z0-9_-]/g, '');
3
+ return '<!doctype html>' +
4
+ '<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">' +
5
+ '<title>Mecord Control Center</title>' +
6
+ '<style nonce="' + safeNonce + '">' +
7
+ ':root{color-scheme:dark;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#0b0d10;color:#eef1f5}' +
8
+ '*{box-sizing:border-box}body{margin:0;min-height:100vh;background:radial-gradient(circle at 15% 0%,#18212d 0,#0b0d10 42%)}' +
9
+ 'button,input,textarea{font:inherit}button{cursor:pointer}.shell{max-width:1440px;margin:auto;padding:24px}.top{display:flex;gap:16px;align-items:center;justify-content:space-between;margin-bottom:20px}' +
10
+ '.brand{display:flex;align-items:center;gap:12px}.mark{width:38px;height:38px;border-radius:12px;background:linear-gradient(145deg,#8aa7ff,#4e68ff);display:grid;place-items:center;font-weight:800;color:#07101f;box-shadow:0 10px 35px #4e68ff33}' +
11
+ 'h1{font-size:20px;margin:0}.muted{color:#8e98a8;font-size:13px}.auth{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}.input{background:#10151c;border:1px solid #26303d;color:#eef1f5;border-radius:10px;padding:9px 11px;outline:none;min-width:220px}.input:focus{border-color:#6e86ff;box-shadow:0 0 0 3px #6e86ff20}' +
12
+ '.btn{border:1px solid #2c3745;background:#161d26;color:#eef1f5;border-radius:10px;padding:9px 12px}.btn:hover{background:#1c2632}.primary{background:#6178ff;border-color:#7186ff;color:white}.danger{border-color:#713b42;color:#ffb9bf}.warn{border-color:#725d31;color:#ffd88a}' +
13
+ '.status{display:flex;align-items:center;gap:8px}.dot{width:8px;height:8px;border-radius:50%;background:#59616d}.dot.ok{background:#49d17d;box-shadow:0 0 16px #49d17d88}.grid{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(320px,.9fr);gap:16px}' +
14
+ '.panel{background:#0f141a;border:1px solid #202935;border-radius:16px;overflow:hidden;box-shadow:0 14px 40px #0004}.panelHead{padding:16px 18px;border-bottom:1px solid #202935;display:flex;align-items:center;justify-content:space-between;gap:12px}.panelHead h2{font-size:14px;margin:0;letter-spacing:.02em}.panelBody{padding:14px 16px}' +
15
+ '.tabs{display:flex;gap:6px;padding:6px;background:#0a0e13;border-bottom:1px solid #202935}.tab{padding:8px 11px;border:0;border-radius:8px;background:transparent;color:#98a3b3}.tab.active{background:#18212b;color:#fff}.hidden{display:none!important}' +
16
+ '.task{padding:13px;border:1px solid #222d3a;border-radius:12px;margin-bottom:9px;background:#111820}.task:hover{border-color:#34465a}.taskTop{display:flex;justify-content:space-between;gap:12px}.taskTitle{font-size:14px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge{font-size:11px;padding:4px 7px;border-radius:999px;background:#202a36;color:#b9c3d0}.badge.VERIFIED{background:#153522;color:#72e49c}.badge.FAILED,.badge.CANCELLED{background:#3c1d22;color:#ff969f}.badge.BLOCKED{background:#392e18;color:#ffd478}.badge.RUNNING{background:#182d4b;color:#8fb9ff}' +
17
+ '.actions{display:flex;gap:7px;flex-wrap:wrap;margin-top:10px}.small{padding:6px 8px;font-size:12px}.empty{padding:30px;text-align:center;color:#7e8998}.detail{white-space:pre-wrap;word-break:break-word;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;background:#090d12;border:1px solid #202935;border-radius:10px;padding:12px;max-height:480px;overflow:auto}' +
18
+ '.field{margin-bottom:11px}.field label{display:block;font-size:12px;color:#95a0af;margin-bottom:6px}.field textarea{width:100%;min-height:88px;resize:vertical}.field .input{width:100%;min-width:0}.two{display:grid;grid-template-columns:1fr 1fr;gap:10px}.event{padding:10px 0;border-bottom:1px solid #1c2530}.event:last-child{border:0}.eventTitle{font-size:12px;color:#d9e0e8}.eventMeta{font-size:11px;color:#798697;margin-top:4px}.device{padding:10px;border:1px solid #222d3a;border-radius:10px;margin-bottom:8px}.toast{position:fixed;right:20px;bottom:20px;max-width:420px;background:#151d27;border:1px solid #354458;border-radius:12px;padding:11px 14px;box-shadow:0 18px 50px #0008;z-index:10}.toast.bad{border-color:#704149;color:#ffc1c6}' +
19
+ '@media(max-width:900px){.grid{grid-template-columns:1fr}.top{align-items:flex-start;flex-direction:column}.auth{justify-content:flex-start;width:100%}.auth .input{flex:1}.shell{padding:14px}.two{grid-template-columns:1fr}}' +
20
+ '</style></head><body><main class="shell">' +
21
+ '<header class="top"><div class="brand"><div class="mark">M</div><div><h1>Mecord Control Center</h1><div class="muted">Local autonomous runtime · authoritative task state</div></div></div>' +
22
+ '<div class="auth"><input id="agentToken" class="input" type="password" autocomplete="off" placeholder="Agent token"><input id="recoveryToken" class="input" type="password" autocomplete="off" placeholder="Recovery token (approvals)"><button id="connect" class="btn primary">Connect</button><button id="refresh" class="btn">Refresh</button></div></header>' +
23
+ '<div class="grid"><section class="panel"><div class="panelHead"><h2>Operations</h2><div class="status"><span id="statusDot" class="dot"></span><span id="statusText" class="muted">Disconnected</span></div></div>' +
24
+ '<div class="tabs"><button class="tab active" data-tab="tasks">Tasks</button><button class="tab" data-tab="teams">Teams</button><button class="tab" data-tab="studio">Studio</button><button class="tab" data-tab="desired">Desired state</button><button class="tab" data-tab="goals">New goal</button><button class="tab" data-tab="activity">Activity</button><button class="tab" data-tab="devices">Devices</button></div>' +
25
+ '<div id="tasks" class="panelBody tabPage"><div id="taskList" class="empty">Connect to load tasks.</div></div>' +
26
+ '<div id="teams" class="panelBody tabPage hidden"><div id="teamList" class="empty">Connect to load Stage-4 missions.</div></div><div id="studio" class="panelBody tabPage hidden"><div class="two"><div class="field"><label>Compiled workflow ID</label><input id="studioWorkflowId" class="input" placeholder="Workflow UUID"></div><div class="field"><label>Values JSON</label><input id="studioValues" class="input" value="{}" spellcheck="false"></div></div><div class="actions"><button id="createStudioRun" class="btn primary">Create run</button></div><div id="studioList" class="empty">No Studio runs loaded.</div></div><div id="desired" class="panelBody tabPage hidden"><div class="field"><label>Desired-state contract JSON</label><textarea id="desiredJson" class="input" spellcheck="false" placeholder="Paste a desired-state contract JSON object"></textarea></div><div class="actions"><button id="createDesired" class="btn primary">Create contract</button></div><div id="desiredList" class="empty">No desired-state contracts loaded.</div></div>' +
27
+ '<div id="goals" class="panelBody tabPage hidden"><div class="field"><label>Objective</label><input id="objective" class="input" placeholder="What should Mecord accomplish?"></div><div class="field"><label>Success conditions · one per line</label><textarea id="success" class="input" placeholder="Build passes&#10;Requested state is verified"></textarea></div><div class="field"><label>Goal JSON</label><textarea id="goalJson" class="input" spellcheck="false" placeholder=\'{"kind":"project-quality-gate","root":"C:\\\\path","checks":["lint","test","build"],"requireAll":true}\'></textarea></div><div class="actions"><button id="submitGoal" class="btn primary">Submit & run</button><button id="queueGoal" class="btn">Queue only</button></div></div>' +
28
+ '<div id="activity" class="panelBody tabPage hidden"><div id="activityList" class="empty">No activity loaded.</div></div>' +
29
+ '<div id="devices" class="panelBody tabPage hidden"><div id="deviceList" class="empty">No devices loaded.</div></div></section>' +
30
+ '<aside class="panel"><div class="panelHead"><h2>Detail</h2><span id="selectedState" class="badge">—</span></div><div class="panelBody"><div id="taskDetail" class="empty">Select a task or team mission to inspect its durable state.</div></div></aside></div></main><div id="toast" class="toast hidden"></div>' +
31
+ '<script nonce="' + safeNonce + '">' +
32
+ '(()=>{"use strict";let token="",selected="",selectedKind="task",poll=null;const $=id=>document.getElementById(id);' +
33
+ 'function toast(message,bad){const n=$("toast");n.textContent=message;n.className="toast"+(bad?" bad":"");clearTimeout(n._t);n._t=setTimeout(()=>n.classList.add("hidden"),4200)}' +
34
+ 'async function api(path,init){if(!token)throw new Error("Connect with the agent token first.");const opts=Object.assign({},init||{});opts.headers=Object.assign({},opts.headers||{},{"authorization":"Bearer "+token});if(opts.body&&!opts.headers["content-type"])opts.headers["content-type"]="application/json";const res=await fetch(path,opts);let body={};try{body=await res.json()}catch{}if(!res.ok)throw new Error((body.error&&body.error.code?body.error.code+": ":"")+(body.error&&body.error.message?body.error.message:"Request failed"));return body}' +
35
+ 'function node(tag,cls,text){const n=document.createElement(tag);if(cls)n.className=cls;if(text!==undefined)n.textContent=String(text);return n}' +
36
+ 'function button(text,cls,fn){const b=node("button","btn small "+(cls||""),text);b.type="button";b.addEventListener("click",e=>{e.stopPropagation();fn()});return b}' +
37
+ 'function stateBadge(state){return node("span","badge "+state,state)}' +
38
+ 'async function loadTasks(){const data=await api("/v1/tasks?limit=100");const box=$("taskList");box.replaceChildren();if(!data.tasks||!data.tasks.length){box.append(node("div","empty","No tasks yet."));return}for(const task of data.tasks){const card=node("div","task");const top=node("div","taskTop");const title=node("div","taskTitle",task.userObjective||task.id);top.append(title,stateBadge(task.state));card.append(top,node("div","muted",(task.id||"")+" · "+(task.updatedAt||"")));const actions=node("div","actions");if(task.state==="PENDING")actions.append(button("Run","primary",()=>control(task.id,"run")));if(task.state==="RUNNING")actions.append(button("Pause","",()=>control(task.id,"pause")),button("Cancel","danger",()=>control(task.id,"cancel")));if(task.state==="PAUSED")actions.append(button("Resume","primary",()=>control(task.id,"resume")),button("Cancel","danger",()=>control(task.id,"cancel")));if(task.state==="BLOCKED")actions.append(button("Review approval","warn",()=>selectTask(task.id)));if(!["VERIFIED","FAILED","CANCELLED"].includes(task.state))actions.append(button("Cancel","danger",()=>control(task.id,"cancel")));card.append(actions);card.addEventListener("click",()=>selectTask(task.id));box.append(card)}}' +
39
+ 'async function loadTeams(){const data=await api("/v1/teams?limit=100");const box=$("teamList");box.replaceChildren();if(!data.teams||!data.teams.length){box.append(node("div","empty","No Stage-4 missions yet."));return}for(const team of data.teams){const card=node("div","task");const top=node("div","taskTop");top.append(node("div","taskTitle",team.objective||team.id),stateBadge(team.state));card.append(top,node("div","muted",(team.id||"")+" · "+(team.updatedAt||"")));const actions=node("div","actions");if(team.state==="PENDING")actions.append(button("Start","primary",()=>teamControl(team.id,"start")));if(team.state==="RUNNING"||team.state==="BLOCKED")actions.append(button("Pause","",()=>teamControl(team.id,"pause")));if(team.state==="PAUSED"||team.state==="BLOCKED")actions.append(button("Resume","primary",()=>teamControl(team.id,"resume")));if(!["VERIFIED","FAILED","CANCELLED"].includes(team.state))actions.append(button("Cancel","danger",()=>teamControl(team.id,"cancel")));card.append(actions);card.addEventListener("click",()=>selectTeam(team.id));box.append(card)}}' +
40
+ 'async function selectTeam(id){selected=id;selectedKind="team";const data=await api("/v1/teams/"+encodeURIComponent(id));const team=data.mission;$("selectedState").textContent=team.state;$("selectedState").className="badge "+team.state;const wrap=node("div");const actions=node("div","actions");if(team.state==="PENDING")actions.append(button("Start","primary",()=>teamControl(id,"start")));if(team.state==="RUNNING"||team.state==="BLOCKED")actions.append(button("Pause","",()=>teamControl(id,"pause")));if(team.state==="PAUSED"||team.state==="BLOCKED")actions.append(button("Resume","primary",()=>teamControl(id,"resume")));if(!["VERIFIED","FAILED","CANCELLED"].includes(team.state))actions.append(button("Cancel","danger",()=>teamControl(id,"cancel")));wrap.append(actions,node("pre","detail",JSON.stringify(team,null,2)));$("taskDetail").replaceChildren(wrap)}' +
41
+ 'async function teamControl(id,op){try{await api("/v1/teams/"+encodeURIComponent(id)+"/"+op,{method:"POST",body:"{}"});await refresh();if(selectedKind==="team"&&selected===id)await selectTeam(id)}catch(e){toast(e.message,true)}}' +
42
+ 'async function selectTask(id){selected=id;selectedKind="task";const data=await api("/v1/tasks/"+encodeURIComponent(id));const task=data.task;$("selectedState").textContent=task.state;$("selectedState").className="badge "+task.state;const wrap=node("div");const actions=node("div","actions");if(task.state==="PENDING")actions.append(button("Run","primary",()=>control(id,"run")));if(task.state==="RUNNING")actions.append(button("Pause","",()=>control(id,"pause")));if(task.state==="PAUSED")actions.append(button("Resume","primary",()=>control(id,"resume")));if(task.state==="BLOCKED"){const record=(task.execution&&task.execution.records||[]).find(r=>r.state==="BLOCKED");if(record)actions.append(button("Approve & resume","warn",()=>approve(id,record.actionId)));}if(!["VERIFIED","FAILED","CANCELLED"].includes(task.state))actions.append(button("Cancel","danger",()=>control(id,"cancel")));wrap.append(actions);const pre=node("pre","detail",JSON.stringify(task,null,2));wrap.append(pre);$("taskDetail").replaceChildren(wrap)}' +
43
+ 'async function control(id,op){try{await api("/v1/tasks/"+encodeURIComponent(id)+"/"+op,{method:"POST",body:"{}"});await refresh();if(selected===id)await selectTask(id)}catch(e){toast(e.message,true)}}' +
44
+ 'async function approve(id,actionId){const recovery=$("recoveryToken").value.trim();if(!recovery){toast("Recovery token is required for explicit approval.",true);return}try{await api("/v1/tasks/"+encodeURIComponent(id)+"/resume",{method:"POST",headers:{"x-operator-recovery-token":recovery},body:JSON.stringify({approvedActionId:actionId})});await refresh();await selectTask(id)}catch(e){toast(e.message,true)}}' +
45
+ 'async function loadStudioRuns(){const data=await api("/v1/studio/runs?limit=100");const box=$("studioList");box.replaceChildren();if(!data.runs||!data.runs.length){box.append(node("div","empty","No Studio runs yet."));return}for(const run of data.runs){const card=node("div","task");const top=node("div","taskTop");top.append(node("div","taskTitle","Workflow "+(run.workflowId||"")),stateBadge(run.state));card.append(top,node("div","muted",(run.id||"")+" · "+(run.updatedAt||"")));const actions=node("div","actions");if(run.state==="PENDING")actions.append(button("Execute next","primary",()=>studioControl(run.id,"execute")));if(!["VERIFIED","FAILED","CANCELLED","RUNNING"].includes(run.state))actions.append(button("Cancel","danger",()=>studioControl(run.id,"cancel")));card.append(actions);card.addEventListener("click",()=>selectStudioRun(run.id));box.append(card)}}' +
46
+ 'async function selectStudioRun(id){selected=id;selectedKind="studio";const data=await api("/v1/studio/runs/"+encodeURIComponent(id));const run=data.run;$("selectedState").textContent=run.state;$("selectedState").className="badge "+run.state;const wrap=node("div");const actions=node("div","actions");if(run.state==="PENDING")actions.append(button("Execute next","primary",()=>studioControl(id,"execute")));if(!["VERIFIED","FAILED","CANCELLED","RUNNING"].includes(run.state))actions.append(button("Cancel","danger",()=>studioControl(id,"cancel")));wrap.append(actions,node("pre","detail",JSON.stringify(run,null,2)));$("taskDetail").replaceChildren(wrap)}' +
47
+ 'async function studioControl(id,op){try{await api("/v1/studio/runs/"+encodeURIComponent(id)+"/"+op,{method:"POST",body:op==="execute"?JSON.stringify({maxSteps:1}):"{}"});await refresh();if(selectedKind==="studio"&&selected===id)await selectStudioRun(id)}catch(e){toast(e.message,true)}}' +
48
+ 'async function createStudioRun(){try{const workflowId=$("studioWorkflowId").value.trim();if(!workflowId)throw new Error("Compiled workflow ID is required.");const values=JSON.parse($("studioValues").value||"{}");const data=await api("/v1/studio/workflows/"+encodeURIComponent(workflowId)+"/run",{method:"POST",body:JSON.stringify({values})});toast("Studio run created.");await loadStudioRuns();if(data.run&&data.run.id)await selectStudioRun(data.run.id)}catch(e){toast(e.message,true)}}' +
49
+ 'async function loadDesired(){const data=await api("/v1/desired-state?limit=100");const box=$("desiredList");box.replaceChildren();if(!data.contracts||!data.contracts.length){box.append(node("div","empty","No desired-state contracts yet."));return}for(const item of data.contracts){const card=node("div","task");const top=node("div","taskTop");top.append(node("div","taskTitle",item.name||item.id),stateBadge(item.status));card.append(top,node("div","muted",(item.id||"")+" · "+(item.updatedAt||"")));const actions=node("div","actions");if(item.status==="PAUSED")actions.append(button("Resume","primary",()=>desiredControl(item.id,"resume")));else actions.append(button("Reconcile","primary",()=>desiredControl(item.id,"reconcile")),button("Pause","",()=>desiredControl(item.id,"pause")));card.append(actions);card.addEventListener("click",()=>selectDesired(item.id));box.append(card)}}' +
50
+ 'async function selectDesired(id){selected=id;selectedKind="desired";const data=await api("/v1/desired-state/"+encodeURIComponent(id));const item=data.contract;$("selectedState").textContent=item.status;$("selectedState").className="badge "+item.status;const wrap=node("div");const actions=node("div","actions");if(item.status==="PAUSED")actions.append(button("Resume","primary",()=>desiredControl(id,"resume")));else actions.append(button("Reconcile","primary",()=>desiredControl(id,"reconcile")),button("Pause","",()=>desiredControl(id,"pause")));wrap.append(actions,node("pre","detail",JSON.stringify(item,null,2)));$("taskDetail").replaceChildren(wrap)}' +
51
+ 'async function desiredControl(id,op){try{await api("/v1/desired-state/"+encodeURIComponent(id)+"/"+op,{method:"POST",body:"{}"});await refresh();if(selectedKind==="desired"&&selected===id)await selectDesired(id)}catch(e){toast(e.message,true)}}' +
52
+ 'async function createDesired(){try{const body=JSON.parse($("desiredJson").value);const data=await api("/v1/desired-state",{method:"POST",body:JSON.stringify(body)});toast("Desired-state contract created.");await loadDesired();if(data.contract&&data.contract.id)await selectDesired(data.contract.id)}catch(e){toast(e.message,true)}}' +
53
+ 'async function loadActivity(){const data=await api("/v1/activity?limit=100");const box=$("activityList");box.replaceChildren();if(!data.events||!data.events.length){box.append(node("div","empty","No activity recorded."));return}for(const ev of data.events.slice().reverse()){const row=node("div","event");row.append(node("div","eventTitle",(ev.capability||ev.type||"event")+" · "+(ev.result||ev.status||"")),node("div","eventMeta",(ev.timestamp||ev.at||"")+" "+(ev.target||"")));box.append(row)}}' +
54
+ 'async function loadDevices(){const data=await api("/v1/devices");const box=$("deviceList");box.replaceChildren();if(data.local){const d=node("div","device");d.append(node("div","taskTitle",data.local.deviceName||"Local device"),node("div","muted",data.local.deviceId||""));box.append(d)}for(const peer of data.peers||[]){const d=node("div","device");d.append(node("div","taskTitle",peer.deviceName||"Paired device"),node("div","muted",(peer.status||"")+" · "+(peer.deviceId||"")));box.append(d)}if(!data.local&&!(data.peers||[]).length)box.append(node("div","empty","No device identity configured."))}' +
55
+ 'async function refresh(){try{await Promise.all([loadTasks(),loadTeams(),loadStudioRuns(),loadDesired(),loadActivity(),loadDevices()]);$("statusDot").className="dot ok";$("statusText").textContent="Connected";if(selected){if(selectedKind==="team")await selectTeam(selected);else if(selectedKind==="studio")await selectStudioRun(selected);else if(selectedKind==="desired")await selectDesired(selected);else await selectTask(selected)}}catch(e){$("statusDot").className="dot";$("statusText").textContent="Disconnected";toast(e.message,true)}}' +
56
+ 'async function submit(run){try{const objective=$("objective").value.trim();if(!objective)throw new Error("Objective is required.");const goal=JSON.parse($("goalJson").value);const success=$("success").value.split(/\\r?\\n/).map(x=>x.trim()).filter(Boolean);if(!success.length)throw new Error("At least one success condition is required.");const data=await api("/v1/goals",{method:"POST",body:JSON.stringify({objective,successConditions:success,goal,run})});toast(run?"Goal submitted and execution started.":"Goal queued.");await refresh();if(data.task&&data.task.id)await selectTask(data.task.id)}catch(e){toast(e.message,true)}}' +
57
+ '$("connect").addEventListener("click",()=>{token=$("agentToken").value.trim();$("agentToken").value="";if(!token){toast("Agent token is required.",true);return}refresh();if(poll)clearInterval(poll);poll=setInterval(()=>{if(token)Promise.all([loadTasks(),loadTeams(),loadStudioRuns(),loadDesired()]).catch(()=>{})},2500)});$("refresh").addEventListener("click",refresh);$("submitGoal").addEventListener("click",()=>submit(true));$("queueGoal").addEventListener("click",()=>submit(false));$("createStudioRun").addEventListener("click",createStudioRun);$("createDesired").addEventListener("click",createDesired);' +
58
+ 'for(const tab of document.querySelectorAll(".tab"))tab.addEventListener("click",()=>{for(const t of document.querySelectorAll(".tab"))t.classList.toggle("active",t===tab);for(const page of document.querySelectorAll(".tabPage"))page.classList.toggle("hidden",page.id!==tab.dataset.tab)});' +
59
+ 'window.addEventListener("beforeunload",()=>{token="";$("recoveryToken").value=""});' +
60
+ '})();</script></body></html>';
61
+ }
@@ -18,7 +18,26 @@ import { OperatorError } from '../../../src/core/errors.js';
18
18
  import { RelayEnrollmentClient } from './relay-enrollment.js';
19
19
  import { DEVELOPER_RELAY_CAPABILITIES } from '../../../src/core/developer-relay-surface.js';
20
20
  import { TaskOrchestrator } from '../../../src/core/task-orchestrator.js';
21
+ import { TeamCoordinator } from '../../../src/core/team-coordinator.js';
22
+ import { ProcedureMemoryStore } from '../../../src/core/procedure-memory.js';
23
+ import { WorldModelStore } from '../../../src/core/world-model.js';
24
+ import { DeviceRoutingStore } from '../../../src/core/device-routing.js';
25
+ import { DevicePoolScheduler } from '../../../src/core/device-pool.js';
26
+ import { ExecutionOptimizerStore } from '../../../src/core/execution-optimizer.js';
27
+ import { OrganizationCoordinator } from '../../../src/core/organization-coordinator.js';
28
+ import { DigitalOperationsLayer } from '../../../src/core/digital-operations.js';
21
29
  import { evidence } from '../../../src/core/evidence.js';
30
+ import { ResourceLeaseStore } from '../../../src/core/resource-leases.js';
31
+ import { TeachModeStore } from '../../../src/core/studio-teach.js';
32
+ import { DesiredStateController } from '../../../src/core/desired-state.js';
33
+ import { DesiredStateReconciler } from '../../../src/core/desired-state-reconciler.js';
34
+ import { DurableEventRuntime } from '../../../src/core/event-runtime.js';
35
+ import { DurableEventTicker } from '../../../src/core/event-ticker.js';
36
+ import { PerceptionGraphStore } from '../../../src/core/perception-graph.js';
37
+ import { publishPerceptionFromActionResult } from '../../../src/core/perception-publication.js';
38
+ import { StudioWorkflowExecutor } from '../../../src/core/studio-executor.js';
39
+ import { SemanticCheckpointManager } from '../../../src/core/semantic-checkpoint.js';
40
+ import { EnterprisePolicyStore } from '../../../src/core/enterprise-policy.js';
22
41
 
23
42
  const allowedRoots = (process.env.OPERATOR_ALLOWED_ROOTS ?? process.cwd())
24
43
  .split(path.delimiter)
@@ -51,13 +70,36 @@ if (recoveryToken !== undefined && recoveryToken.length < 32) {
51
70
  }
52
71
 
53
72
  const stateDir = path.resolve(process.env.OPERATOR_STATE_DIR ?? path.join(os.homedir(), '.operator'));
73
+ const permissions = {
74
+ allowedCapabilities: ['computer.inspect', 'project.inspect', 'project.command.*', 'project.transaction.*', 'docker.*', 'compute.run', 'postgres.*', 'vscode.*', 'file.*', 'git.*', 'terminal.execute', 'terminal.session', 'process.inspect', 'process.manage', 'browser.inspect', 'browser.navigate', 'browser.interact', 'app.inspect', 'app.operate', 'visual.capture', 'input.operate', 'perception.*'],
75
+ allowedRoots,
76
+ allowExternalWrites: false,
77
+ allowSystemChanges: false,
78
+ allowDestructive: false
79
+ };
54
80
  const emergencyStop = new EmergencyStopStore(stateDir);
55
81
  const approvals = new ApprovalStore(stateDir);
56
82
  const sessionApprovals = new SessionApprovalStore();
57
83
  const audit = new AuditLog(stateDir);
58
84
  const tasks = new TaskStore(stateDir);
85
+ const resourceLeases = new ResourceLeaseStore(stateDir);
86
+ const teams = new TeamCoordinator(stateDir);
87
+ const procedures = new ProcedureMemoryStore(stateDir);
88
+ const world = new WorldModelStore(stateDir);
89
+ const perception = new PerceptionGraphStore(stateDir);
90
+ const optimizer = new ExecutionOptimizerStore(stateDir);
59
91
  const deviceIdentity = new DeviceIdentityStore(stateDir);
60
92
  const deviceRegistry = new DeviceRegistryStore(stateDir);
93
+ const semanticMigration = new SemanticCheckpointManager(stateDir, {
94
+ identity: deviceIdentity,
95
+ registry: deviceRegistry
96
+ });
97
+ const deviceRouting = new DeviceRoutingStore(stateDir, deviceRegistry);
98
+ const devicePool = new DevicePoolScheduler(stateDir, deviceRegistry, deviceRouting);
99
+ const organizations = new OrganizationCoordinator(stateDir, teams);
100
+ const enterprisePolicy = new EnterprisePolicyStore(stateDir);
101
+ const teachMode = new TeachModeStore(stateDir);
102
+ const events = new DurableEventRuntime(stateDir);
61
103
  const privacy = new LocalPrivacyDataStore(stateDir);
62
104
  const browserAutoLaunch = process.env.OPERATOR_BROWSER_AUTO_LAUNCH !== '0';
63
105
  const relayUrl = process.env.OPERATOR_RELAY_URL?.trim();
@@ -84,6 +126,8 @@ const runtime = createRuntime({
84
126
  terminalAllowedExecutables,
85
127
  projectCommandRegistryPath: process.env.OPERATOR_PROJECT_COMMAND_REGISTRY,
86
128
  dockerExecutable: process.env.OPERATOR_DOCKER_PATH,
129
+ computeJavascriptImage: process.env.OPERATOR_COMPUTE_JS_IMAGE,
130
+ computePythonImage: process.env.OPERATOR_COMPUTE_PY_IMAGE,
87
131
  postgresProfileRegistryPath: process.env.OPERATOR_POSTGRES_PROFILE_REGISTRY,
88
132
  psqlExecutable: process.env.OPERATOR_PSQL_PATH,
89
133
  vscodeExecutable: process.env.OPERATOR_VSCODE_PATH,
@@ -93,7 +137,42 @@ const runtime = createRuntime({
93
137
  browserPath: process.env.OPERATOR_BROWSER_PATH,
94
138
  browserDataDir: process.env.OPERATOR_BROWSER_DATA_DIR,
95
139
  windowsUiaPath: process.env.OPERATOR_WINDOWS_UIA_PATH,
96
- windowsPathLeasePath: process.env.OPERATOR_WINDOWS_PATH_LEASE_PATH
140
+ windowsPathLeasePath: process.env.OPERATOR_WINDOWS_PATH_LEASE_PATH,
141
+ perception
142
+ });
143
+ const studioExecutor = new StudioWorkflowExecutor(stateDir, {
144
+ teach: teachMode,
145
+ runtime,
146
+ leases: resourceLeases,
147
+ permissions
148
+ });
149
+ const recoveredStudioRuns = await studioExecutor.recoverInterrupted();
150
+ const operationCapabilities = await runtime.supportedCapabilities(permissions.allowedCapabilities);
151
+ const operations = new DigitalOperationsLayer(stateDir, {
152
+ procedures,
153
+ world,
154
+ devices: devicePool,
155
+ optimizer,
156
+ teams,
157
+ organizations,
158
+ availableCapabilities: operationCapabilities
159
+ });
160
+ const desiredState = new DesiredStateController(stateDir, { world, operations });
161
+ const desiredStateIntervalMs = Number(process.env.OPERATOR_DESIRED_STATE_INTERVAL_MS ?? 60_000);
162
+ if (!Number.isSafeInteger(desiredStateIntervalMs) || desiredStateIntervalMs < 1_000 || desiredStateIntervalMs > 24 * 60 * 60_000) {
163
+ throw new OperatorError('DESIRED_STATE_INTERVAL_INVALID', 'OPERATOR_DESIRED_STATE_INTERVAL_MS must be an integer from 1000 to 86400000.');
164
+ }
165
+ const desiredStateReconciler = new DesiredStateReconciler(desiredState, {
166
+ intervalMs: desiredStateIntervalMs,
167
+ onError: (error) => console.error(`[operator] desired-state reconcile tick failed: ${error instanceof Error ? error.message : String(error)}`)
168
+ });
169
+ const eventTickIntervalMs = Number(process.env.OPERATOR_EVENT_TICK_INTERVAL_MS ?? 1_000);
170
+ if (!Number.isSafeInteger(eventTickIntervalMs) || eventTickIntervalMs < 250 || eventTickIntervalMs > 60_000) {
171
+ throw new OperatorError('EVENT_TICK_INTERVAL_INVALID', 'OPERATOR_EVENT_TICK_INTERVAL_MS must be an integer from 250 to 60000.');
172
+ }
173
+ const eventTicker = new DurableEventTicker(events, {
174
+ intervalMs: eventTickIntervalMs,
175
+ onError: (error) => console.error(`[operator] event tick failed: ${error instanceof Error ? error.message : String(error)}`)
97
176
  });
98
177
  let relayRunner = null;
99
178
  let relayRun = null;
@@ -111,7 +190,7 @@ async function failRequiredRelay(error ) {
111
190
  const message = error instanceof Error ? error.message : String(error);
112
191
  console.error(`[operator] required relay failed: ${message}`);
113
192
  stopRelay();
114
- await Promise.allSettled([agent.close(), runtime.close()]);
193
+ await Promise.allSettled([desiredStateReconciler.stop(), eventTicker.stop(), agent.close(), runtime.close()]);
115
194
  process.exitCode = 1;
116
195
  setImmediate(() => process.exit(1));
117
196
  }
@@ -153,6 +232,15 @@ function startRelay() {
153
232
  localAgentBaseUrl,
154
233
  agentToken: token,
155
234
  getSupportedCapabilities: () => runtime.supportedCapabilities(DEVELOPER_RELAY_CAPABILITIES),
235
+ onStatus: (status) => {
236
+ if (status.state === 'socket-connected') {
237
+ console.error('[operator] relay socket connected; authenticating');
238
+ } else if (status.state === 'authenticated-ready') {
239
+ console.error(`[operator] relay authenticated and ready (${status.capabilityCount} capabilities)`);
240
+ } else {
241
+ console.error(`[operator] relay reconnect scheduled (${status.code}, ${status.delayMs}ms)`);
242
+ }
243
+ },
156
244
  allowLoopbackInsecure: relayAllowInsecureLoopback
157
245
  });
158
246
  const runner = relayRunner;
@@ -193,18 +281,12 @@ async function resetLocalDevice() {
193
281
  return await coordinator.reset();
194
282
  }
195
283
 
196
- const permissions = {
197
- allowedCapabilities: ['computer.inspect', 'project.inspect', 'project.command.*', 'project.transaction.*', 'docker.*', 'postgres.*', 'vscode.*', 'file.*', 'git.*', 'terminal.execute', 'browser.inspect', 'browser.navigate', 'browser.interact', 'app.inspect', 'app.operate'],
198
- allowedRoots,
199
- allowExternalWrites: false,
200
- allowSystemChanges: false,
201
- allowDestructive: false
202
- };
203
284
  const taskOrchestrator = new TaskOrchestrator({
204
285
  runtime,
205
286
  store: tasks,
206
287
  permissions,
207
- executeAction: async (action, actionPermissions) => {
288
+ resourceLeases,
289
+ executeAction: async (action, actionPermissions, context) => {
208
290
  if ((await emergencyStop.status()).engaged) {
209
291
  return {
210
292
  ok: false,
@@ -215,14 +297,33 @@ const taskOrchestrator = new TaskOrchestrator({
215
297
  durationMs: 0
216
298
  };
217
299
  }
218
- const result = await runtime.execute(action, actionPermissions);
300
+ const result = await runtime.execute(action, actionPermissions, context);
301
+ try {
302
+ await publishPerceptionFromActionResult(perception, action, result);
303
+ } catch (error) {
304
+ await audit.append({
305
+ ...(action.taskId ? { traceId: action.taskId, taskId: action.taskId } : {}),
306
+ actionId: action.id,
307
+ providerId: 'perception.graph',
308
+ capability: 'perception.publish',
309
+ result: 'failure',
310
+ risk: 'write',
311
+ details: { code: typeof (error )?.code === 'string' ? (error ).code : 'PERCEPTION_PUBLICATION_FAILED' }
312
+ });
313
+ }
219
314
  await audit.append({
220
- taskId: action.taskId,
315
+ ...(action.taskId ? { traceId: action.taskId, taskId: action.taskId } : {}),
316
+ actionId: action.id,
317
+ providerId: result.provider,
221
318
  capability: action.capability,
222
319
  target: action.target,
223
320
  result: result.ok ? 'success' : result.provider === 'policy' ? 'blocked' : 'failure',
224
321
  risk: action.risk,
225
- details: { actionId: action.id, provider: result.provider, durationMs: result.durationMs, errorCode: result.error?.code }
322
+ details: {
323
+ durationMs: result.durationMs,
324
+ errorCode: result.error?.code,
325
+ sideEffectState: result.error?.sideEffectState
326
+ }
226
327
  });
227
328
  return result;
228
329
  }
@@ -238,6 +339,20 @@ const agent = createLocalAgentServer({
238
339
  audit,
239
340
  tasks,
240
341
  taskOrchestrator,
342
+ teams,
343
+ procedures,
344
+ world,
345
+ devicePool,
346
+ optimizer,
347
+ organizations,
348
+ operations,
349
+ events,
350
+ perception,
351
+ teachMode,
352
+ studioExecutor,
353
+ semanticMigration,
354
+ enterprisePolicy,
355
+ desiredState,
241
356
  deviceIdentity,
242
357
  deviceRegistry,
243
358
  privacy,
@@ -257,6 +372,14 @@ const agent = createLocalAgentServer({
257
372
  postgresConfigured: Boolean(process.env.OPERATOR_POSTGRES_PROFILE_REGISTRY),
258
373
  vscodeConfigured: Boolean(process.env.OPERATOR_VSCODE_PATH),
259
374
  windowsUiaConfigured: Boolean(process.env.OPERATOR_WINDOWS_UIA_PATH),
375
+ studioWorkflowExecutorConfigured: true,
376
+ semanticMigrationConfigured: true,
377
+ enterprisePolicyConfigured: await enterprisePolicy.isConfigured(),
378
+ recoveredStudioRunCount: recoveredStudioRuns,
379
+ desiredStateReconcilerConfigured: true,
380
+ desiredStateIntervalMs,
381
+ eventRuntimeConfigured: true,
382
+ eventTickIntervalMs,
260
383
  relayConfigured: Boolean(relayUrl),
261
384
  relayResultConfigured: Boolean(relayResultUrl),
262
385
  relayTokenFileConfigured: Boolean(relayUrl),
@@ -280,6 +403,11 @@ console.error(`[operator] protected state directory: ${stateDir}`);
280
403
  console.error(`[operator] recovery API: ${recoveryToken ? 'configured' : 'disabled until OPERATOR_RECOVERY_TOKEN is set'}`);
281
404
  console.error(`[operator] generic terminal: ${terminalAllowedExecutables.length ? 'explicit allowlist configured' : 'disabled by default'}`);
282
405
  console.error(`[operator] relay: ${relayUrl ? 'configured' : 'disabled'}`);
406
+ console.error(`[operator] studio workflow recovery: ${recoveredStudioRuns} interrupted run(s) reconciled`);
407
+ console.error(`[operator] desired-state reconciler: every ${desiredStateIntervalMs}ms`);
408
+ console.error(`[operator] durable event ticker: every ${eventTickIntervalMs}ms`);
409
+ desiredStateReconciler.start();
410
+ eventTicker.start();
283
411
  if (relayUrl) {
284
412
  const relayCapabilities = await runtime.supportedCapabilities(DEVELOPER_RELAY_CAPABILITIES);
285
413
  console.error(`[operator] relay capabilities: ${relayCapabilities.join(', ') || 'none'}`);
@@ -306,7 +434,7 @@ for (const signal of ['SIGINT', 'SIGTERM'] ) {
306
434
  if (shuttingDown) return;
307
435
  shuttingDown = true;
308
436
  stopRelay();
309
- await Promise.allSettled([relayRun, agent.close(), runtime.close()].filter(Boolean) );
437
+ await Promise.allSettled([relayRun, desiredStateReconciler.stop(), eventTicker.stop(), agent.close(), runtime.close()].filter(Boolean) );
310
438
  process.exit(0);
311
439
  });
312
440
  }
@@ -0,0 +1,194 @@
1
+ import crypto from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
3
+ import fs from 'node:fs/promises';
4
+
5
+ import { OperatorError } from '../../../src/core/errors.js';
6
+ import { PathScope } from '../../../src/capabilities/path-scope.js';
7
+ import { semanticCheckpointDigest, } from '../../../src/core/semantic-checkpoint.js';
8
+ import { worldValueDigest, } from '../../../src/core/world-model.js';
9
+
10
+ const CAPABILITY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
11
+
12
+ export function migrationAuthorityDigest(input
13
+
14
+
15
+ ) {
16
+ const requiredCapabilities = exactCapabilities(input.requiredCapabilities);
17
+ const resourceKeys = normalizedResourceKeys(input.resourceKeys);
18
+ return semanticCheckpointDigest({
19
+ version: 1,
20
+ requiredCapabilities,
21
+ resourceKeys
22
+ });
23
+ }
24
+
25
+ export function assertMigrationCapabilities(input
26
+
27
+
28
+
29
+ ) {
30
+ const required = exactCapabilities(input.requiredCapabilities);
31
+ const supported = new Set(input.supportedCapabilities);
32
+ for (const capability of required) {
33
+ if (!profileAllows(capability, input.permissions.allowedCapabilities)) {
34
+ throw new OperatorError('SEMANTIC_MIGRATION_CAPABILITY_DENIED', `Capability ${capability} is outside local authority.`);
35
+ }
36
+ if (!supported.has(capability)) {
37
+ throw new OperatorError('SEMANTIC_MIGRATION_CAPABILITY_UNAVAILABLE', `Capability ${capability} is not supported on this device.`);
38
+ }
39
+ }
40
+ return required;
41
+ }
42
+
43
+ export async function verifyMigrationResourceKey(
44
+ resourceKeyInput ,
45
+ permissions ,
46
+ requiredCapabilitiesInput
47
+ ) {
48
+ const resourceKey = bounded(resourceKeyInput, 1024, 'resourceKey');
49
+ const required = exactCapabilities(requiredCapabilitiesInput);
50
+ const allowFamily = (prefixes ) => required.some((capability) => prefixes.some((prefix) => capability.startsWith(prefix)))
51
+ && required.every((capability) => profileAllows(capability, permissions.allowedCapabilities));
52
+
53
+ const physical = physicalResource(resourceKey);
54
+ if (physical) {
55
+ if (permissions.allowedRoots.length < 1 || !allowFamily(physical.capabilityPrefixes)) return false;
56
+ try {
57
+ const scope = new PathScope(permissions.allowedRoots);
58
+ await scope.resolveExisting(physical.path);
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ if (resourceKey.startsWith('browser:')) return allowFamily(['browser.']);
66
+ if (resourceKey === 'desktop:windows') return allowFamily(['app.', 'visual.', 'input.']);
67
+ if (resourceKey.startsWith('process:')) return allowFamily(['process.', 'terminal.']);
68
+ if (resourceKey.startsWith('cap:')) {
69
+ const capability = resourceKey.slice('cap:'.length);
70
+ return CAPABILITY.test(capability)
71
+ && required.includes(capability)
72
+ && profileAllows(capability, permissions.allowedCapabilities);
73
+ }
74
+ return false;
75
+ }
76
+
77
+ export async function hashAuthorizedMigrationArtifact(
78
+ filePathInput ,
79
+ permissions
80
+ ) {
81
+ if (permissions.allowedRoots.length < 1) throw new OperatorError('SEMANTIC_MIGRATION_ARTIFACT_DENIED', 'No filesystem roots are authorized.');
82
+ const scope = new PathScope(permissions.allowedRoots);
83
+ return await scope.withExisting(bounded(filePathInput, 4096, 'artifact path'), async (resolved) => {
84
+ const stat = await fs.lstat(resolved);
85
+ if (!stat.isFile() || stat.isSymbolicLink()) {
86
+ throw new OperatorError('SEMANTIC_MIGRATION_ARTIFACT_DENIED', 'Migration artifacts must be regular files.');
87
+ }
88
+ if (!Number.isSafeInteger(stat.size) || stat.size < 0) throw new OperatorError('SEMANTIC_MIGRATION_ARTIFACT_DENIED', 'Migration artifact size is invalid.');
89
+ const hash = crypto.createHash('sha256');
90
+ for await (const chunk of createReadStream(resolved, { highWaterMark: 1024 * 1024 })) hash.update(chunk );
91
+ return { digest: hash.digest('hex'), size: stat.size };
92
+ });
93
+ }
94
+
95
+ export async function buildMigrationArtifacts(
96
+ artifactsInput ,
97
+ permissions
98
+ ) {
99
+ if (!Array.isArray(artifactsInput) || artifactsInput.length > 5000) {
100
+ throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'artifacts must contain at most 5000 entries.');
101
+ }
102
+ const seen = new Set ();
103
+ const artifacts = [];
104
+ for (const [index, item] of artifactsInput.entries()) {
105
+ const key = bounded(item?.key, 1024, `artifacts[${index}].key`);
106
+ if (seen.has(key)) throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'Artifact keys must be unique.');
107
+ seen.add(key);
108
+ const proof = await hashAuthorizedMigrationArtifact(item?.path, permissions);
109
+ artifacts.push({ key, digest: proof.digest, size: proof.size });
110
+ }
111
+ return artifacts.sort((a, b) => a.key.localeCompare(b.key));
112
+ }
113
+
114
+ export async function buildMigrationWorldAssumptions(
115
+ assumptionsInput ,
116
+ world
117
+ ) {
118
+ if (!Array.isArray(assumptionsInput) || assumptionsInput.length > 5000) {
119
+ throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'worldAssumptions must contain at most 5000 entries.');
120
+ }
121
+ const assumptions = [];
122
+ const seen = new Set ();
123
+ for (const [index, item] of assumptionsInput.entries()) {
124
+ const entityKey = bounded(item?.entityKey, 512, `worldAssumptions[${index}].entityKey`);
125
+ const factKey = bounded(item?.factKey, 128, `worldAssumptions[${index}].factKey`);
126
+ const identity = `${entityKey}\0${factKey}`;
127
+ if (seen.has(identity)) throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'World assumptions must be unique.');
128
+ seen.add(identity);
129
+ const fact = await world.resolveFact(entityKey, factKey);
130
+ if (fact.status !== 'resolved') {
131
+ throw new OperatorError('SEMANTIC_MIGRATION_WORLD_UNRESOLVED', `World assumption ${entityKey}.${factKey} is ${fact.status}.`);
132
+ }
133
+ assumptions.push({ entityKey, factKey, valueDigest: worldValueDigest(fact.value) });
134
+ }
135
+ return assumptions.sort((a, b) => `${a.entityKey}\0${a.factKey}`.localeCompare(`${b.entityKey}\0${b.factKey}`));
136
+ }
137
+
138
+ export async function verifyMigrationWorldAssumption(
139
+ assumption ,
140
+ world
141
+ ) {
142
+ const fact = await world.resolveFact(assumption.entityKey, assumption.factKey);
143
+ return fact.status === 'resolved' ? worldValueDigest(fact.value) : undefined;
144
+ }
145
+
146
+ function physicalResource(resourceKey ) {
147
+ for (const [prefix, families] of [
148
+ ['file:', ['file.']],
149
+ ['repo:', ['git.', 'project.']],
150
+ ['workspace:', ['terminal.', 'project.']],
151
+ ['docker:', ['docker.']]
152
+ ] ) {
153
+ if (resourceKey.startsWith(prefix)) {
154
+ const target = resourceKey.slice(prefix.length);
155
+ return target ? { path: target, capabilityPrefixes: [...families] } : undefined;
156
+ }
157
+ }
158
+ if (resourceKey.startsWith('database:')) {
159
+ const value = resourceKey.slice('database:'.length);
160
+ const split = value.lastIndexOf(':');
161
+ const root = split > 0 ? value.slice(0, split) : value;
162
+ return root ? { path: root, capabilityPrefixes: ['postgres.'] } : undefined;
163
+ }
164
+ return undefined;
165
+ }
166
+
167
+ function profileAllows(capability , allowed ) {
168
+ return allowed.some((rule) => rule === capability || (rule.endsWith('.*') && capability.startsWith(rule.slice(0, -1))));
169
+ }
170
+
171
+ function exactCapabilities(input ) {
172
+ if (!Array.isArray(input) || input.length > 2048) throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'requiredCapabilities is invalid.');
173
+ const values = input.map((item, index) => {
174
+ const value = bounded(item, 256, `requiredCapabilities[${index}]`);
175
+ if (!CAPABILITY.test(value) || value.includes('*')) throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'Migration capabilities must be exact capability names.');
176
+ return value;
177
+ });
178
+ if (new Set(values).size !== values.length) throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'Migration capabilities contain duplicates.');
179
+ return values.sort();
180
+ }
181
+
182
+ function normalizedResourceKeys(input ) {
183
+ if (!Array.isArray(input) || input.length > 5000) throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'resourceKeys is invalid.');
184
+ const values = input.map((item, index) => bounded(item, 1024, `resourceKeys[${index}]`));
185
+ if (new Set(values).size !== values.length) throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', 'resourceKeys contain duplicates.');
186
+ return values.sort();
187
+ }
188
+
189
+ function bounded(input , max , label ) {
190
+ if (typeof input !== 'string' || input.length < 1 || input.length > max || input.includes('\0')) {
191
+ throw new OperatorError('SEMANTIC_MIGRATION_INPUT_INVALID', `${label} is invalid.`);
192
+ }
193
+ return input;
194
+ }