@ornexus/neocortex 4.60.54 → 4.60.56

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 (53) hide show
  1. package/dist/sbom.cdx.json +23 -23
  2. package/install.ps1 +1 -1
  3. package/install.sh +1 -1
  4. package/package.json +5 -2
  5. package/packages/client/dist/cli.js +0 -0
  6. package/packages/client/dist/commands/invoke.d.ts +32 -0
  7. package/packages/client/dist/commands/invoke.js +69 -60
  8. package/packages/client/dist/generated/command-registry.generated.d.ts +2 -2
  9. package/packages/client/dist/generated/command-registry.generated.js +1 -1
  10. package/packages/client/dist/generated/public-projections.generated.d.ts +20 -20
  11. package/packages/client/dist/generated/public-projections.generated.js +1 -1
  12. package/packages/client/dist/git/direct-yolo-checkpoint.d.ts +6 -0
  13. package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -4
  14. package/packages/client/dist/private-execution/clean-baseline.d.ts +12 -0
  15. package/packages/client/dist/private-execution/clean-baseline.js +2 -0
  16. package/packages/client/dist/private-execution/codex-executor.d.ts +65 -0
  17. package/packages/client/dist/private-execution/codex-executor.js +2 -0
  18. package/packages/client/dist/private-execution/delivery.d.ts +74 -0
  19. package/packages/client/dist/private-execution/delivery.js +3 -0
  20. package/packages/client/dist/private-execution/isolated-admission.d.ts +13 -0
  21. package/packages/client/dist/private-execution/isolated-admission.js +2 -0
  22. package/packages/client/dist/private-execution/isolated-session.d.ts +41 -0
  23. package/packages/client/dist/private-execution/isolated-session.js +2 -0
  24. package/packages/client/dist/private-execution/isolated-workspace.d.ts +13 -0
  25. package/packages/client/dist/private-execution/isolated-workspace.js +1 -0
  26. package/packages/client/dist/private-execution/root-checkpoint.d.ts +15 -0
  27. package/packages/client/dist/private-execution/root-checkpoint.js +1 -0
  28. package/packages/client/dist/resolvers/remote-resolver.js +1 -1
  29. package/packages/client/dist/runner-cli.js +0 -0
  30. package/packages/client/dist/yolo/change-evidence-collector.d.ts +6 -0
  31. package/packages/client/dist/yolo/change-evidence-collector.js +1 -0
  32. package/packages/client/dist/yolo/spec-envelope-collector.d.ts +11 -0
  33. package/packages/client/dist/yolo/spec-envelope-collector.js +5 -0
  34. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +1 -1
  35. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
  36. package/packages/shared/dist/frontmatter-context-contract.js +326 -0
  37. package/packages/shared/dist/generated/command-registry.generated.js +2 -2
  38. package/packages/shared/dist/generated/invocation-schema.generated.js +16 -4
  39. package/packages/shared/dist/generated/public-projections.generated.js +18 -18
  40. package/packages/shared/dist/invocation/index.d.ts +1 -1
  41. package/packages/shared/dist/yolo/change-evidence.js +44 -0
  42. package/packages/shared/dist/yolo/spec-envelope.js +161 -0
  43. package/targets-stubs/antigravity/gemini.md +1 -1
  44. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  45. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  46. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  47. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  48. package/targets-stubs/claude-code/neocortex.md +2 -2
  49. package/targets-stubs/codex/AGENTS.md +1 -1
  50. package/targets-stubs/cursor/agent.md +2 -2
  51. package/targets-stubs/gemini-cli/agent.md +2 -2
  52. package/targets-stubs/opencode/neocortex-root.md +1 -1
  53. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -0,0 +1,6 @@
1
+ import { type YoloChangeEvidenceV1 } from '../../../shared/dist/yolo/change-evidence.js';
2
+ export declare function collectYoloChangeEvidence(options: {
3
+ readonly projectRoot: string;
4
+ readonly defaultBranch: string;
5
+ readonly expectedHeadSha: string;
6
+ }): YoloChangeEvidenceV1;
@@ -0,0 +1 @@
1
+ import{execFileSync as h}from"node:child_process";import{realpathSync as c}from"node:fs";import{createYoloChangeEvidenceV1 as d}from"../../../shared/dist/yolo/change-evidence.js";function r(e,o){try{return h("git",["-C",e,...o],{encoding:"utf8",timeout:3e3,maxBuffer:1024*1024,stdio:["ignore","pipe","ignore"]})}catch{throw new Error("yolo-change-evidence-unavailable")}}function g(e){if(!/^[A-Za-z0-9][A-Za-z0-9_./-]{0,119}$/.test(e.defaultBranch)||e.defaultBranch.includes(".."))throw new Error("yolo-change-evidence-unavailable");const o=r(e.projectRoot,["rev-parse","--show-toplevel"]).trim();if(c(o)!==c(e.projectRoot))throw new Error("yolo-change-evidence-unavailable");const a=r(e.projectRoot,["rev-parse","HEAD"]).trim();if(a!==e.expectedHeadSha)throw new Error("yolo-change-evidence-stale");let t;try{t=r(e.projectRoot,["merge-base","HEAD",e.defaultBranch]).trim()}catch{t=r(e.projectRoot,["merge-base","HEAD",`refs/remotes/origin/${e.defaultBranch}`]).trim()}const n=r(e.projectRoot,["diff","--name-only","-z",t,"--"]).split("\0").filter(Boolean),l=r(e.projectRoot,["ls-files","--others","--exclude-standard","-z"]).split("\0").filter(Boolean),i=d({baseSha:t,headSha:a,changedPaths:[...n,...l]});if(r(e.projectRoot,["rev-parse","HEAD"]).trim()!==a)throw new Error("yolo-change-evidence-stale");return i}export{g as collectYoloChangeEvidence};
@@ -0,0 +1,11 @@
1
+ import { type YoloSpecEnvelopeV1 } from '../../../shared/dist/yolo/spec-envelope.js';
2
+ export interface CollectYoloSpecEnvelopeOptions {
3
+ readonly projectRoot: string;
4
+ readonly storyId: string;
5
+ readonly epicId: string;
6
+ readonly inputSha256: string;
7
+ /** Test seam; production callers omit it and read Git HEAD physically. */
8
+ readonly headSha?: string;
9
+ readonly collectedAt?: string;
10
+ }
11
+ export declare function collectYoloSpecEnvelope(options: CollectYoloSpecEnvelopeOptions): YoloSpecEnvelopeV1;
@@ -0,0 +1,5 @@
1
+ import{execFileSync as h}from"node:child_process";import{existsSync as w,readFileSync as S,realpathSync as d,statSync as m}from"node:fs";import{join as f,sep as E}from"node:path";import{createYoloSpecEnvelopeV1 as R,YOLO_SPEC_ARTIFACT_LIMIT_BYTES as v}from"../../../shared/dist/yolo/spec-envelope.js";import{inspectFrontmatterContext as g}from"../../../shared/dist/frontmatter-context-contract.js";const j=["core/templates/specs/feature-spec-template.md","core/templates/specs/architecture-spec-template.md","core/templates/specs/contract-spec-template.md","core/templates/specs/story-spec-template.md","core/templates/specs/traceability-matrix-template.md"],x=/(?:^|[\s([`'";])((?:docs\/(?:stories|epics|specs|architecture|examples)\/|core\/templates\/specs\/)[A-Za-z0-9_./-]+\.(?:md|mdx|feature))(?:$|[\s)\]`'",;])/gm;function A(e){return e.startsWith("core/templates/")?"template":e.startsWith("docs/epics/")?"epic":e.startsWith("docs/stories/")&&e.endsWith(".story.md")?"story":e.toLowerCase().includes("adr")?"adr":e.toLowerCase().includes("contract")?"contract":e.toLowerCase().includes("nfr")?"nfr":e.endsWith(".feature")?"bdd":e.toLowerCase().includes("traceability")?"traceability":e.startsWith("docs/examples/")?"example":"spec"}function I(e){return[...new Set([...e.matchAll(x)].map(t=>t[1]).filter(t=>!!t))].sort()}function n(e,t){const c=d(e);let r;try{r=d(f(c,t))}catch{throw new Error("yolo-spec-envelope-required")}if(!r.startsWith(`${c}${E}`))throw new Error("yolo-spec-envelope-path-out-of-scope");if(!m(r).isFile())throw new Error("yolo-spec-envelope-unsafe");if(m(r).size>v)throw new Error("yolo-spec-envelope-too-large");return S(r,"utf8")}function a(e,t){const r=g(e,{context:t==="story"?"story-target":"epic-target",explicitlySelected:!0});if(r.status==="malformed"||r.status==="oversized")throw new Error("yolo-spec-envelope-unsafe");if(r.status==="absent")return e;const s=e.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/);if(!s)throw new Error("yolo-spec-envelope-unsafe");return`---
2
+ ${Object.entries(r.metadata).map(([i,p])=>`${i}: ${JSON.stringify(p)}`).join(`
3
+ `)}
4
+ ---
5
+ ${e.slice(s[0].length)}`}function u(e){try{return h("git",["-C",e,"rev-parse","HEAD"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim()}catch{throw new Error("yolo-spec-envelope-stale")}}function L(e){const t=`docs/stories/${e.storyId}.story.md`,c=`docs/epics/epic-${e.epicId}.md`,r=e.headSha??u(e.projectRoot),s=a(n(e.projectRoot,t),"story"),l=a(n(e.projectRoot,c),"epic"),i=[{kind:"story",path:t,content:s},{kind:"epic",path:c,content:l}];for(const o of new Set([...I(s),...j]))o===t||o===c||!w(f(e.projectRoot,o))||i.push({kind:A(o),path:o,content:n(e.projectRoot,o)});const p=R({storyId:e.storyId,epicId:e.epicId,targetPath:t,inputSha256:e.inputSha256,headSha:r,collectedAt:e.collectedAt??new Date().toISOString(),artifacts:i}),y=e.headSha??u(e.projectRoot);if(r!==y||a(n(e.projectRoot,t),"story")!==s||a(n(e.projectRoot,c),"epic")!==l||i.some(o=>o.kind!=="story"&&o.kind!=="epic"&&n(e.projectRoot,o.path)!==o.content))throw new Error("yolo-spec-envelope-stale");return p}export{L as collectYoloSpecEnvelope};
@@ -144,7 +144,7 @@ export declare class YoloopClientStateStore {
144
144
  private syncStateDirectory;
145
145
  private withMutationLock;
146
146
  private transitionRun;
147
- /** Replace one proven exhausted cap run with a fresh created successor. */
147
+ /** Replace a proven exhausted run, or an explicitly reopened success, with a fresh successor. */
148
148
  private startSuccessorRun;
149
149
  /**
150
150
  * Fire-and-forget chmod/ACL. Uses sync helpers from secure-config.ts
@@ -1 +1 @@
1
- import{promises as u}from"node:fs";import{createHash as b}from"node:crypto";import{join as _,dirname as V}from"node:path";import{hostname as S}from"node:os";import{setSecureFilePermissions as $,setSecureDirPermissions as q}from"../config/secure-config.js";import{RECOVERY_HISTORY_CAP as C}from"./shared-yoloop-types.js";const B=".neocortex",g="yoloop-state.json",j="yoloop.lock",J="yoloop-state.mutation.lock",P="yoloop-session-rollover.json",A="yoloop-successor-transition.json",z=360*60*1e3,W=3e4,G=200,x=Object.freeze(["rollover-session-v1","successor-transition-v1"]),X={warn:()=>{},info:()=>{},error:()=>{}};function w(t){const e=t.slice(0,10);return{version:"1.1",lastResetAt:t,storiesRunToday:0,iterationsToday:0,iterationsTodayDate:e,tokensUsedToday:0,lastRunAt:null,coolDownUntil:null,totalRunsAllTime:0,totalStoriesAllTime:0,currentSession:null,continuationPolicy:"p205-finite-resume-v2",pendingContinuationAuthorities:[],continuationReceipts:[],continuationRecoveryReceipts:[]}}function we(t){return{lock:null,state:w(t),capabilities:x}}class fe{stateDir;statePath;lockPath;mutationLockPath;rolloverManifestPath;successorTransitionManifestPath;logger;constructor(e,s=X){this.stateDir=_(e,B),this.statePath=_(this.stateDir,g),this.lockPath=_(this.stateDir,j),this.mutationLockPath=_(this.stateDir,J),this.rolloverManifestPath=_(this.stateDir,P),this.successorTransitionManifestPath=_(this.stateDir,A),this.logger=s}async readAll(e){const s=e??new Date().toISOString();await this.cleanupOrphanTempFiles(),await this.recoverSessionRolloverIfPresent(s),await this.recoverSuccessorTransitionIfPresent(s);const[n,r]=await Promise.all([this.readLock(),this.readState(s)]);return{lock:await this.prepareLockForSnapshot(n,s),state:r,capabilities:x}}async applyUpdate(e,s){const n=s??new Date().toISOString();await this.ensureDir(),await this.recoverSessionRolloverIfPresent(n),await this.recoverSuccessorTransitionIfPresent(n);for(const r of e.operations)await this.applyOperation(r,n)}async requestRunTransition(e,s){const n=s??new Date().toISOString();return await this.ensureDir(),this.withMutationLock(()=>this.transitionRun(e,n))}async requestLifecycleAction(e,s={},n){const r=n??new Date().toISOString();return await this.ensureDir(),this.withMutationLock(async()=>{const i=(await this.readState(r)).runControl;if(!i)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");const a=Date.parse(r).toString(36),c=s.operationId??`op-${e}-${a}`,l=s.idempotencyKey??`idem-${e}-${a}`,h=i.receipts.find(O=>O.operationId===c||O.idempotencyKey===l);if(h){if(h.operationId===c&&h.idempotencyKey===l)return h;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}const y=ae(i.status,e);if(!y)throw new Error(E.has(i.status)?"YOLOOP_RUN_TERMINAL":"YOLOOP_RUN_TRANSITION_INVALID");return this.transitionRun(ce({current:i,action:e,target:y,operationId:c,idempotencyKey:l,occurredAt:r}),r)})}async applyOperation(e,s){switch(e.type){case"acquire_lock":await this.acquireLock(e.lock,s);return;case"release_lock":await this.releaseLock(e);return;case"write_state":await this.withMutationLock(async()=>{const n=await this.readState(s),{runControl:r,...o}=e.state;await this.writeState({...o,...n.runControl?{version:"1.2",runControl:n.runControl}:{}})});return;case"advance_epic":await this.advanceEpic(e.nextEpicIndex,e.nextEpicId,s);return;case"no_op":this.logger.info({event:"yoloop_noop"},"yoloop server sent no-op");return;case"reserve_continuation_authority":await this.reserveContinuationAuthority(e.authority,s);return;case"acknowledge_continuation_authority":await this.acknowledgeContinuationAuthority(e.idempotencyKey,e.receipt,s);return;case"repair_continuation_authority":await this.repairContinuationAuthority(e.beforeHash,e.authority,e.receipt,s);return;case"switch_continuation_policy":await this.switchContinuationPolicy(e.policy,e.session,s);return;case"transition_run":await this.requestRunTransition(e,s);return;case"start_successor_run":await this.withMutationLock(()=>this.startSuccessorRun(e,s));return;case"commit_successor_transition":await this.withMutationLock(()=>this.commitSuccessorTransition(e,s));return;case"rollover_session":await this.withMutationLock(()=>this.rolloverSession(e,s));return;case"recovery_attempted":await this.appendRecoveryAttempt(e.attempt,s);return;case"recovery_resolved":await this.resolveRecoveryAttempt(e.attempt_id,e.outcome,s);return;default:{const n=e;throw this.logger.error({event:"yoloop_unknown_op",op:n},"unknown yoloop operation rejected"),new Error("YOLOOP_STATE_OPERATION_UNSUPPORTED")}}}async reserveContinuationAuthority(e,s){const n=await this.readState(s),r=n.pendingContinuationAuthorities??[],o=typeof e.idempotencyKey=="string"?e.idempotencyKey:"";if(!o)throw new Error("YOLOOP_CONTINUATION_AUTHORITY_INVALID");const i=r.find(a=>a.idempotencyKey===o);if(i){if(d(i)===d(e))return;throw new Error("YOLOOP_CONTINUATION_AUTHORITY_CONFLICT")}if(r.length>=64)throw new Error("YOLOOP_PENDING_AUTHORITY_CAPACITY_EXCEEDED");await this.writeState({...n,pendingContinuationAuthorities:[...r,e]})}async acknowledgeContinuationAuthority(e,s,n){const r=await this.readState(n),o=r.pendingContinuationAuthorities??[],i=o.findIndex(h=>h.idempotencyKey===e);if(i<0)throw new Error("YOLOOP_CONTINUATION_RECEIPT_ORPHAN");const a=o[i];if(s.idempotencyKey!==e||s.sessionId!==a.sessionId||JSON.stringify(s.storyIds)!==JSON.stringify(a.storyIds))throw new Error("YOLOOP_CONTINUATION_RECEIPT_MISMATCH");const c=o.filter((h,y)=>y!==i),l=r.continuationReceipts??[];await this.writeState({...r,pendingContinuationAuthorities:c,continuationReceipts:[...l.slice(-63),s]})}async repairContinuationAuthority(e,s,n,r){const o=await this.readState(r),i=o.pendingContinuationAuthorities??[],a=o.continuationRecoveryReceipts??[],c=typeof n.idempotencyKey=="string"?n.idempotencyKey:"",l=a.find(O=>O.idempotencyKey===c);if(l){if(d(l)===d(n))return;throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_CONFLICT")}if(i.length!==1||p("p208-legacy-authority-before-v1",i[0])!==e)throw new Error("YOLOOP_CONTINUATION_RECOVERY_BEFORE_MISMATCH");const h=p("p208-legacy-authority-after-v1",s);if(n.beforeHash!==e||n.afterHash!==h)throw new Error("YOLOOP_CONTINUATION_RECOVERY_HASH_MISMATCH");const y={...n};if(delete y.idempotencyKey,!c||p("p208-legacy-authority-recovery-receipt-v1",y)!==c)throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_INVALID");await this.writeState({...o,pendingContinuationAuthorities:[s],continuationRecoveryReceipts:[...a.slice(-63),n]})}async switchContinuationPolicy(e,s,n){const r=await this.readState(n);if(r.currentSession||(r.pendingContinuationAuthorities?.length??0)>0)throw new Error("YOLOOP_POLICY_SWITCH_ACTIVE");try{await u.copyFile(this.statePath,`${this.statePath}.policy-backup`)}catch(o){if(o.code!=="ENOENT")throw o}await this.writeState({...r,continuationPolicy:e,continuationSession:s})}async appendRecoveryAttempt(e,s){const n=await this.readState(s),r=n.recovery_history??[];if(r.some(c=>c.attempted_at===e.attempted_at&&c.story_id===e.story_id)){this.logger.warn({event:"recovery_attempted_duplicate",story_id:e.story_id},"duplicate recovery_attempted op; skipping");return}const i=K([...r,e]),a={...n,recovery_history:i};await this.writeState(a),this.logger.info({event:"recovery_attempted_persisted",story_id:e.story_id,history_size:i.length},"recovery attempt persisted")}async resolveRecoveryAttempt(e,s,n){const r=await this.readState(n),o=r.recovery_history??[],i=o.findIndex(h=>h.recovery_step_id_tokenized===e);if(i===-1){this.logger.warn({event:"recovery_resolve_orphan",attempt_id:e},"recovery_resolved op had no matching attempt; skipping");return}const a=o[i];if(a.outcome===s&&a.finished_at){this.logger.info({event:"recovery_resolved_duplicate",attempt_id:e,outcome:s},"duplicate recovery_resolved op; skipping");return}const c=o.slice();c[i]={...a,outcome:s,finished_at:n};const l={...r,recovery_history:c};await this.writeState(l),this.logger.info({event:"recovery_resolved_persisted",attempt_id:e,outcome:s},"recovery attempt resolved")}async acquireLock(e,s){const n={...e,pid:process.pid,hostname:S(),ownerKind:"client",startedAt:e.startedAt||s,leaseId:e.leaseId??le(e.sessionId,s),leaseState:"active",leaseAcquiredAt:e.leaseAcquiredAt??s,leaseDurationMs:e.leaseDurationMs??z,leaseExpiresAt:e.leaseExpiresAt??e.ttlExpiresAt},r=JSON.stringify(n,null,2);try{const o=await u.open(this.lockPath,"wx");try{await o.writeFile(r,"utf8")}finally{await o.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired",pid:n.pid,sessionId:n.sessionId},"yoloop lock acquired");return}catch(o){const i=o;if(i.code!=="EEXIST")throw this.logger.error({event:"yoloop_lock_acquire_error",err:i.message},"failed to acquire yoloop lock"),o;const a=await this.readLock();if(a&&this.isSameClientSessionLock(a,n)){this.logger.info({event:"yoloop_lock_acquire_duplicate",sessionId:n.sessionId},"duplicate acquire_lock for current yoloop session; treating as no-op");return}if(!await this.maybeRecoverOrphanLock(s))throw this.logger.warn({event:"yoloop_lock_held"},"yoloop lock held by another live process"),o;const l=await u.open(this.lockPath,"wx");try{await l.writeFile(r,"utf8")}finally{await l.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired_after_recovery",pid:n.pid},"yoloop lock acquired after orphan recovery")}}async releaseLock(e){const s=await this.readState(new Date().toISOString()),n=await this.readLock();if(s.runControl){if(!e.expectedRunId||!e.expectedSessionId||!e.expectedLeaseId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(e.expectedRunId!==s.runControl.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(!n||e.expectedSessionId!==n.sessionId||e.expectedLeaseId!==n.leaseId)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}try{await u.unlink(this.lockPath),this.logger.info({event:"yoloop_lock_released"},"yoloop lock released")}catch(r){const o=r;if(o.code==="ENOENT"){this.logger.info({event:"yoloop_lock_release_noop"},"yoloop lock release noop (file absent)");return}this.logger.warn({event:"yoloop_lock_release_error",err:o.message},"unexpected error releasing yoloop lock")}}async readLock(){try{const e=await u.readFile(this.lockPath,"utf8"),s=JSON.parse(e),n=s.currentEpicId??s.epicId??"";return{pid:s.pid??0,hostname:s.hostname??"",...s.ownerKind?{ownerKind:s.ownerKind}:{},...s.ownerPidState?{ownerPidState:s.ownerPidState}:{},...typeof s.leaseId=="string"?{leaseId:s.leaseId}:{},...s.leaseState?{leaseState:s.leaseState}:{},...typeof s.leaseAcquiredAt=="string"?{leaseAcquiredAt:s.leaseAcquiredAt}:{},...typeof s.leaseDurationMs=="number"?{leaseDurationMs:s.leaseDurationMs}:{},...typeof s.leaseExpiresAt=="string"?{leaseExpiresAt:s.leaseExpiresAt}:{},startedAt:s.startedAt??"",sessionId:s.sessionId??"",mode:s.mode??"epic-id",inputPath:s.inputPath??"",epicQueue:s.epicQueue??(n?[n]:[]),currentEpicIndex:s.currentEpicIndex??0,currentEpicId:n,ttlExpiresAt:s.ttlExpiresAt??"",epicId:s.epicId??n}}catch{return null}}async readState(e){try{const s=await u.readFile(this.statePath,"utf8"),n=JSON.parse(s);return he(n,e)}catch(s){const n=s;return n.code!=="ENOENT"&&this.logger.warn({event:"yoloop_state_read_error",err:n.message},"yoloop state file unreadable; using defaults"),w(e)}}async writeState(e){const s=`${this.statePath}.tmp.${process.pid}.${Date.now()}`,n=JSON.stringify(e,null,2);try{const r=await u.open(s,"w",384);try{await r.writeFile(n,"utf8"),await r.sync()}finally{await r.close()}this.trySetFilePerms(s),await u.rename(s,this.statePath),await this.syncStateDirectory()}catch(r){const o=r;this.logger.error({event:"yoloop_state_write_error",err:o.message},"failed to write yoloop state");try{await u.unlink(s)}catch{}throw r}}async rolloverSession(e,s){v(e);const n=await this.readRolloverManifest();if(n){if(oe(n.operation,e)){if(n.operation.payloadHash!==e.payloadHash)throw new Error("YOLOOP_SESSION_ROLLOVER_CONFLICT");if(n.phase==="applied")return;await this.completePreparedSessionRollover(n.operation,s);return}else if(n.phase==="prepared")throw new Error("YOLOOP_SESSION_ROLLOVER_CONFLICT")}const r=await this.readState(s),o=await this.readLock();H(e,r,o),await this.writeRolloverManifest({schemaVersion:"YoloopSessionRolloverManifest/1.0",phase:"prepared",operation:e}),await this.completePreparedSessionRollover(e,s)}async completePreparedSessionRollover(e,s){const n=await this.readState(s);let r=d(n)===d(e.next);if(!r){const i=await this.readLock();H(e,n,i),await this.writeState(e.next),r=!0}const o=await this.readLock();if(e.expectedSessionId===null||e.expectedLeaseId===null){if(e.expectedSessionId!==null||e.expectedLeaseId!==null||o)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT")}else if(o){if(o.sessionId!==e.expectedSessionId||o.leaseId!==e.expectedLeaseId)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT");await u.unlink(this.lockPath),await this.syncStateDirectory()}else if(!r)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT");await this.writeRolloverManifest({schemaVersion:"YoloopSessionRolloverManifest/1.0",phase:"applied",operation:e}),this.logger.info({event:"yoloop_session_rollover_applied",runId:e.runId,operationId:e.operationId,coolDownUntil:e.coolDownUntil},"yoloop per-run-cap session rollover applied")}async recoverSessionRolloverIfPresent(e){try{await u.access(this.rolloverManifestPath)}catch(s){if(s.code==="ENOENT")return;throw s}await this.ensureDir(),await this.withMutationLock(async()=>{const s=await this.readRolloverManifest();s?.phase==="prepared"&&await this.completePreparedSessionRollover(s.operation,e)})}async readRolloverManifest(){try{const e=JSON.parse(await u.readFile(this.rolloverManifestPath,"utf8"));if(e.schemaVersion!=="YoloopSessionRolloverManifest/1.0"||e.phase!=="prepared"&&e.phase!=="applied"||!e.operation||e.operation.type!=="rollover_session")throw new Error("YOLOOP_SESSION_ROLLOVER_MANIFEST_CORRUPT");return v(e.operation),e}catch(e){if(e.code==="ENOENT")return null;throw e.message==="YOLOOP_SESSION_ROLLOVER_MANIFEST_CORRUPT"?e:new Error("YOLOOP_SESSION_ROLLOVER_MANIFEST_CORRUPT")}}async writeRolloverManifest(e){const s=`${this.rolloverManifestPath}.tmp.${process.pid}.${Date.now()}`,n=await u.open(s,"w",384);try{await n.writeFile(JSON.stringify(e,null,2),"utf8"),await n.sync()}finally{await n.close()}this.trySetFilePerms(s),await u.rename(s,this.rolloverManifestPath),await this.syncStateDirectory()}async commitSuccessorTransition(e,s){f(e);const n=await this.readSuccessorTransitionManifest();if(n){if(se(n.operation,e)){if(n.operation.payloadHash!==e.payloadHash)throw new Error("YOLOOP_SUCCESSOR_TRANSITION_CONFLICT");if(n.phase==="applied")return;await this.completePreparedSuccessorTransition(n.operation,s);return}if(n.phase==="prepared")throw new Error("YOLOOP_SUCCESSOR_TRANSITION_CONFLICT")}const r=await this.readState(s);if(R(e,r)){await this.writeSuccessorTransitionManifest({schemaVersion:"YoloopSuccessorTransitionManifest/1.0",phase:"applied",operation:e});return}k(e,r),await this.writeSuccessorTransitionManifest({schemaVersion:"YoloopSuccessorTransitionManifest/1.0",phase:"prepared",operation:e}),await this.completePreparedSuccessorTransition(e,s)}async completePreparedSuccessorTransition(e,s){let n=await this.readState(s);if(!R(e,n)){d(n.runControl)===d(e.successor.next)||(await this.startSuccessorRun(e.successor,s),n=await this.readState(s));const o=re(e,n);await this.writeState(o),n=await this.readState(s)}if(!R(e,n))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_STATE_CONFLICT");await this.writeSuccessorTransitionManifest({schemaVersion:"YoloopSuccessorTransitionManifest/1.0",phase:"applied",operation:e}),this.logger.info({event:"yoloop_successor_transition_applied",runId:e.successor.runId,operationId:e.operationId},"yoloop successor transition applied")}async recoverSuccessorTransitionIfPresent(e){try{await u.access(this.successorTransitionManifestPath)}catch(s){if(s.code==="ENOENT")return;throw s}await this.ensureDir(),await this.withMutationLock(async()=>{const s=await this.readSuccessorTransitionManifest();s?.phase==="prepared"&&await this.completePreparedSuccessorTransition(s.operation,e)})}async readSuccessorTransitionManifest(){try{const e=JSON.parse(await u.readFile(this.successorTransitionManifestPath,"utf8"));if(e.schemaVersion!=="YoloopSuccessorTransitionManifest/1.0"||e.phase!=="prepared"&&e.phase!=="applied"||!e.operation||e.operation.type!=="commit_successor_transition")throw new Error("YOLOOP_SUCCESSOR_TRANSITION_MANIFEST_CORRUPT");return f(e.operation),e}catch(e){if(e.code==="ENOENT")return null;throw e.message==="YOLOOP_SUCCESSOR_TRANSITION_MANIFEST_CORRUPT"?e:new Error("YOLOOP_SUCCESSOR_TRANSITION_MANIFEST_CORRUPT")}}async writeSuccessorTransitionManifest(e){const s=`${this.successorTransitionManifestPath}.tmp.${process.pid}.${Date.now()}`,n=await u.open(s,"w",384);try{await n.writeFile(JSON.stringify(e,null,2),"utf8"),await n.sync()}finally{await n.close()}this.trySetFilePerms(s),await u.rename(s,this.successorTransitionManifestPath),await this.syncStateDirectory()}async advanceEpic(e,s,n){const r=await this.readState(n);if(!r.currentSession){this.logger.warn({event:"yoloop_advance_no_session",nextEpicId:s},"advance_epic with no active session; skipping");return}const o={...r,currentSession:{...r.currentSession,currentEpicIndex:e}};await this.writeState(o),this.logger.info({event:"yoloop_epic_advance",nextEpicId:s,nextIndex:e},"yoloop advanced to next epic")}async ensureDir(){try{await u.mkdir(this.stateDir,{recursive:!0}),this.trySetDirPerms(this.stateDir)}catch(e){const s=e;throw this.logger.error({event:"yoloop_mkdir_error",err:s.message,path:this.stateDir},"failed to ensure .neocortex/ directory"),e}}async syncStateDirectory(){try{const e=await u.open(this.stateDir,"r");try{await e.sync()}finally{await e.close()}}catch(e){const s=e.code;if(s!=="EINVAL"&&s!=="EISDIR"&&s!=="ENOTSUP"&&s!=="EPERM")throw e}}async withMutationLock(e){let s=null;for(let n=0;n<G;n+=1)try{s=await u.open(this.mutationLockPath,"wx",384),await s.writeFile(JSON.stringify({pid:process.pid,acquiredAt:new Date().toISOString()})),await s.sync();break}catch(r){if(r.code!=="EEXIST")throw r;try{const o=await u.stat(this.mutationLockPath);if(Date.now()-o.mtimeMs>W){await u.unlink(this.mutationLockPath);continue}}catch{continue}await new Promise(o=>setTimeout(o,5))}if(!s)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");try{return await e()}finally{await s.close().catch(()=>{}),await u.unlink(this.mutationLockPath).catch(()=>{})}}async transitionRun(e,s){T(e);const n=await this.readState(s),r=n.runControl,o=r?.receipts.find(l=>l.operationId===e.operationId||l.idempotencyKey===e.idempotencyKey);if(o){if(o.operationId===e.operationId&&o.idempotencyKey===e.idempotencyKey&&o.payloadHash===e.payloadHash)return o;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}if(r&&r.runId!==e.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if((r?.revision??0)!==e.expectedRevision)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if((r?.latestSnapshotHash??null)!==e.expectedSnapshotHash)throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT");L(r,e);const c=e.next.receipts.at(-1);if(!c)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT");return await this.writeState({...n,version:"1.2",runControl:e.next}),this.logger.info({event:"yoloop_run_transition_committed",runId:e.runId,operationId:e.operationId,revision:c.revision,status:c.status},"yoloop run transition committed"),c}async startSuccessorRun(e,s){Y(e);const n=await this.readState(s),r=n.runControl,o={schemaVersion:"YoloopRunPredecessor/1.0",runId:e.previousRunId,revision:e.expectedRevision,snapshotHash:e.expectedSnapshotHash,successorOperationId:e.operationId,publicSafe:!0},i=r?.receipts.find(l=>l.operationId===e.operationId||l.idempotencyKey===e.idempotencyKey);if(i){if(i.operationId===e.operationId&&i.idempotencyKey===e.idempotencyKey&&e.next.receipts.at(-1)?.payloadHash===i.payloadHash&&d(r?.predecessor)===d(o))return i;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}if(!r||r.runId!==e.previousRunId||r.status!=="exhausted"||r.revision!==e.expectedRevision||r.latestSnapshotHash!==e.expectedSnapshotHash||n.currentSession!==null)throw new Error("YOLOOP_RUN_SUCCESSOR_CONFLICT");const a=e.next.receipts.at(-1);if(!a||d(e.next.predecessor)!==d(o))throw new Error("YOLOOP_RUN_SUCCESSOR_CONFLICT");const c={type:"transition_run",runId:e.runId,operationId:e.operationId,idempotencyKey:e.idempotencyKey,expectedRevision:0,expectedSnapshotHash:null,payloadHash:a.payloadHash,next:e.next};return T(c),L(void 0,c),await this.writeState({...n,version:"1.2",runControl:e.next}),a}trySetFilePerms(e){try{$(e)}catch{}}trySetDirPerms(e){try{q(e)}catch{}}async prepareLockForSnapshot(e,s){if(!e)return null;if(e.ownerKind!=="client")return e;const n=F(e,s),r={...e,leaseState:n};if(n==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"lease_expired"},"expired client-owned yoloop lock lease recovered before snapshot echo"),null;if(!r.pid||r.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"invalid_pid"},"invalid PID in client-owned yoloop lock; recovered"),null;if(r.hostname&&r.hostname!==S())return{...r,ownerPidState:"unknown"};try{return process.kill(r.pid,0),{...r,ownerPidState:"live"}}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"dead_pid"},"stale client-owned yoloop lock recovered before snapshot echo"),null}}async maybeRecoverOrphanLock(e){const s=await this.readLock();if(!s)return!0;if(F(s,e)==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"lease_expired"},"expired yoloop lock lease recovered"),!0;if(!s.pid||s.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"invalid_pid"},"invalid PID in lock file; recovered"),!0;if(s.hostname&&s.hostname!==S())return this.logger.warn({event:"yoloop_lock_remote_host",ownerKind:s.ownerKind??"server",ownerPidState:"unknown"},"lock held by a remote host; honoring"),!1;try{return process.kill(s.pid,0),!1}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"dead_pid"},"orphan yoloop lock recovered"),!0}}async deleteLockBestEffort(){try{await u.unlink(this.lockPath)}catch{}}isSameClientSessionLock(e,s){return e.ownerKind==="client"&&e.sessionId===s.sessionId&&e.pid===process.pid&&e.hostname===S()}async cleanupOrphanTempFiles(){try{const e=V(this.statePath),s=await u.readdir(e),n=[`${g}.tmp.`,`${P}.tmp.`,`${A}.tmp.`],r=s.filter(o=>n.some(i=>o.startsWith(i)));for(const o of r)try{await u.unlink(_(e,o))}catch{}}catch{}}}function d(t){return t===null||typeof t!="object"?JSON.stringify(t):Array.isArray(t)?`[${t.map(d).join(",")}]`:`{${Object.entries(t).filter(([,e])=>e!==void 0).sort(([e],[s])=>e.localeCompare(s)).map(([e,s])=>`${JSON.stringify(e)}:${d(s)}`).join(",")}}`}function p(t,e){return b("sha256").update(`${t}\0${d(e)}`).digest("hex")}function Z(t){return p("p9390812898200217364-yoloop-session-v1",t)}function Q(t){return p("p9390812898200217364-yoloop-rollover-v1",t)}function ee(t){return p("p9390812898200217364-yoloop-successor-run-v1",t)}function te(t){return p("p9046281030721413419-yoloop-successor-transition-v1",t)}function se(t,e){return t.operationId===e.operationId&&t.idempotencyKey===e.idempotencyKey}function f(t){const e=/^[A-Za-z0-9._:-]{1,128}$/;Y(t.successor);const s={operationId:t.operationId,idempotencyKey:t.idempotencyKey,successor:t.successor,state:t.state,...t.acknowledgement?{acknowledgement:t.acknowledgement}:{},...t.reservation?{reservation:t.reservation}:{}},n=t.acknowledgement?.idempotencyKey,r=t.acknowledgement?.receipt.idempotencyKey,o=t.reservation?.authority.idempotencyKey;if(t.successor.type!=="start_successor_run"||!e.test(t.operationId)||!e.test(t.idempotencyKey)||t.operationId!==t.successor.operationId||t.idempotencyKey!==t.successor.idempotencyKey||!/^[a-f0-9]{64}$/.test(t.payloadHash)||t.payloadHash!==te(s)||Buffer.byteLength(d(s),"utf8")>1048576||t.acknowledgement!==void 0&&(t.acknowledgement.type!=="acknowledge_continuation_authority"||typeof n!="string"||!e.test(n)||r!==n)||t.reservation!==void 0&&(t.reservation.type!=="reserve_continuation_authority"||typeof o!="string"||!e.test(o))||n!==void 0&&o===n)throw new Error("YOLOOP_SUCCESSOR_TRANSITION_CONFLICT")}function k(t,e){if(f(t),!(d(e.runControl)===d(t.successor.next))){const n=e.runControl;if(!n||n.runId!==t.successor.previousRunId||n.status!=="exhausted"||n.revision!==t.successor.expectedRevision||n.latestSnapshotHash!==t.successor.expectedSnapshotHash||e.currentSession!==null)throw new Error("YOLOOP_SUCCESSOR_TRANSITION_STATE_CONFLICT")}ne(t,e)}function ne(t,e){const s=e.pendingContinuationAuthorities??[],n=e.continuationReceipts??[],r=t.acknowledgement;if(r){const l=s.find(O=>O.idempotencyKey===r.idempotencyKey);if(!l||r.receipt.sessionId!==l.sessionId||d(r.receipt.storyIds)!==d(l.storyIds))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT");const h=s.filter(O=>O.idempotencyKey!==r.idempotencyKey),y=[...n.slice(-63),r.receipt];if(d(t.state.pendingContinuationAuthorities??[])!==d(h)||d(t.state.continuationReceipts??[])!==d(y))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT")}else if(d(t.state.pendingContinuationAuthorities??[])!==d(s)||d(t.state.continuationReceipts??[])!==d(n))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT");const o=t.reservation?.authority;if(!o)return;const i=String(o.idempotencyKey??""),a=t.state.pendingContinuationAuthorities??[],c=a.find(l=>l.idempotencyKey===i);if(c&&d(c)!==d(o))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT");if(!c&&a.length>=64)throw new Error("YOLOOP_PENDING_AUTHORITY_CAPACITY_EXCEEDED")}function re(t,e){k(t,e);const s=t.reservation?.authority,n=[...t.state.pendingContinuationAuthorities??[]];return s&&!n.some(r=>r.idempotencyKey===s.idempotencyKey)&&n.push(s),{...t.state,version:"1.2",runControl:t.successor.next,...s||t.state.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:n}:{}}}function R(t,e){const s=t.reservation?.authority,n=[...t.state.pendingContinuationAuthorities??[]];s&&!n.some(o=>o.idempotencyKey===s.idempotencyKey)&&n.push(s);const r={...t.state,version:"1.2",runControl:t.successor.next,...s||t.state.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:n}:{}};return d(e)===d(r)}function oe(t,e){return t.operationId===e.operationId&&t.idempotencyKey===e.idempotencyKey}function v(t){const e=/^[A-Za-z0-9._:-]{1,128}$/,s={runId:t.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.expectedRevision,expectedSnapshotHash:t.expectedSnapshotHash,expectedSessionId:t.expectedSessionId,expectedSessionFingerprint:t.expectedSessionFingerprint,expectedLeaseId:t.expectedLeaseId,coolDownUntil:t.coolDownUntil,reason:t.reason,exhaustedRun:t.exhaustedRun,next:t.next};if(!e.test(t.runId)||!e.test(t.operationId)||!e.test(t.idempotencyKey)||!/^[a-f0-9]{64}$/.test(t.expectedSessionFingerprint)||!/^[a-f0-9]{64}$/.test(t.payloadHash)||!Number.isSafeInteger(t.expectedRevision)||t.expectedRevision<0||!Number.isFinite(Date.parse(t.coolDownUntil))||!["YOLOOP_PER_RUN_CAP_REACHED","YOLOOP_SESSION_TIMEOUT"].includes(t.reason)||t.payloadHash!==Q(s)||t.receipt.schemaVersion!=="YoloopSessionRolloverReceipt/1.0"||t.receipt.runId!==t.runId||t.receipt.operationId!==t.operationId||t.receipt.idempotencyKey!==t.idempotencyKey||t.receipt.payloadHash!==t.payloadHash||t.receipt.coolDownUntil!==t.coolDownUntil||t.receipt.revision!==(t.exhaustedRun?.revision??0)||t.receipt.snapshotHash!==(t.exhaustedRun?.latestSnapshotHash??null)||t.receipt.status!=="exhausted"||t.receipt.publicSafe!==!0)throw new Error("YOLOOP_SESSION_ROLLOVER_CONFLICT");if(t.expectedSessionId===null!=(t.expectedLeaseId===null)||t.expectedSessionId!==null&&!e.test(t.expectedSessionId)||t.expectedLeaseId!==null&&!e.test(t.expectedLeaseId))throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT")}function Y(t){const e=/^[A-Za-z0-9._:-]{1,128}$/,s={runId:t.runId,previousRunId:t.previousRunId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.expectedRevision,expectedSnapshotHash:t.expectedSnapshotHash,next:t.next};if(!e.test(t.runId)||!e.test(t.previousRunId)||t.runId===t.previousRunId||!e.test(t.operationId)||!e.test(t.idempotencyKey)||!/^[a-f0-9]{64}$/.test(t.payloadHash)||!Number.isSafeInteger(t.expectedRevision)||t.expectedRevision<1||t.payloadHash!==ee(s))throw new Error("YOLOOP_RUN_SUCCESSOR_CONFLICT")}function H(t,e,s){v(t);const n=e.currentSession;if(!n||Z(n)!==t.expectedSessionFingerprint)throw new Error("YOLOOP_SESSION_ROLLOVER_SESSION_CONFLICT");const r=e.runControl;if((r?.revision??0)!==t.expectedRevision||(r?.latestSnapshotHash??null)!==t.expectedSnapshotHash||r&&r.runId!==t.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(t.expectedSessionId===null||t.expectedLeaseId===null){if(s)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT")}else if(!s||s.sessionId!==t.expectedSessionId||s.leaseId!==t.expectedLeaseId)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT");const o=t.exhaustedRun;if(r)if(r.status==="exhausted"){if(d(o)!==d(r))throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT")}else{if(!o||o.status!=="exhausted")throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT");const a=o.receipts.at(-1);if(!a)throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT");const c={type:"transition_run",runId:r.runId,operationId:a.operationId,idempotencyKey:a.idempotencyKey,expectedRevision:r.revision,expectedSnapshotHash:r.latestSnapshotHash,payloadHash:a.payloadHash,next:o};T(c),L(r,c)}else{const a=o?.receipts[0],c=o?.receipts[1];if(o&&(o.runId!==t.runId||o.status!=="exhausted"||o.revision!==2||o.events.length!==2||o.progressSnapshots.length!==4||o.receipts.length!==2||a?.runId!==t.runId||a.revision!==1||a.status!=="created"||c?.runId!==t.runId||c.revision!==2||c.status!=="exhausted"||c.operationId!==`${t.operationId}:run`||c.idempotencyKey!==`${t.idempotencyKey}:run`||o.latestSnapshotHash!==c.snapshotHash||o.predecessor!==void 0))throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT")}const i={...e,version:"1.2",coolDownUntil:t.coolDownUntil,currentSession:null,...o?{runControl:o}:{}};if(d(i)!==d(t.next))throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT")}function U(t){const{receipts:e,...s}=t.next;return p("p220-yoloop-run-transition-payload-v1",{runId:t.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.expectedRevision,expectedSnapshotHash:t.expectedSnapshotHash,next:s})}const E=new Set(["success","partial","blocked","failure","exhausted","cancelled"]),ie={created:["running","cancelling","exhausted"],running:["pausing","cancelling","success","partial","blocked","failure","exhausted"],pausing:["paused","cancelling","failure","exhausted"],paused:["resuming","cancelling","exhausted"],resuming:["running","cancelling","failure","exhausted"],cancelling:["cancelled","failure","exhausted"]};function ae(t,e){return e==="pause"?t==="running"?"pausing":null:e==="resume"?t==="paused"?"resuming":null:E.has(t)||t==="cancelling"?null:"cancelling"}function ce(t){const e=t.current.revision+1,s=t.action==="cancel"?["YOLOOP_RUN_CANCEL_REQUESTED"]:[],n={schemaVersion:"YoloopOperationalBudgetSummary/1.0",limits:t.current.operationalBudget.limits,usage:t.current.usage,exhaustedDimension:null},r={schemaVersion:"ProgressSnapshot/1.0",runId:t.current.runId,sequence:t.current.progressSnapshots.length+1,revision:e,operationId:t.operationId,boundary:t.action,phase:"before",status:t.current.status,occurredAt:t.occurredAt,parentSnapshotHash:t.current.latestSnapshotHash,budget:n,reasonCodes:s,publicSafe:!0},o=p("p220-yoloop-progress-snapshot-id-v1",{runId:r.runId,operationId:r.operationId,sequence:r.sequence,phase:r.phase}),i={...r,snapshotId:o,snapshotHash:p("p220-yoloop-progress-snapshot-v1",{...r,snapshotId:o})},a={...r,sequence:i.sequence+1,phase:"after",status:t.target,parentSnapshotHash:i.snapshotHash},c=p("p220-yoloop-progress-snapshot-id-v1",{runId:a.runId,operationId:a.operationId,sequence:a.sequence,phase:a.phase}),l={...a,snapshotId:c,snapshotHash:p("p220-yoloop-progress-snapshot-v1",{...a,snapshotId:c})},h={schemaVersion:"YoloopRunEvent/1.0",runId:t.current.runId,operationId:t.operationId,kind:"intent",sequence:t.current.events.length+1,revision:e,occurredAt:t.occurredAt,fromStatus:t.current.status,toStatus:t.target,reasonCodes:s,budgetDimension:null,parentEventHash:t.current.events.at(-1)?.eventHash??null,publicSafe:!0},y=p("p220-yoloop-run-event-id-v1",{runId:h.runId,operationId:h.operationId,sequence:h.sequence}),O={...h,eventId:y,eventHash:p("p220-yoloop-run-event-v1",{...h,eventId:y})},I={...t.current,revision:e,status:t.target,events:[...t.current.events,O],progressSnapshots:[...t.current.progressSnapshots,i,l],latestSnapshotHash:l.snapshotHash,receipts:[...t.current.receipts]},N={runId:t.current.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.current.revision,expectedSnapshotHash:t.current.latestSnapshotHash,next:I},m=U(N),M={schemaVersion:"YoloopRunTransitionReceipt/1.0",runId:t.current.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,payloadHash:m,revision:e,snapshotHash:l.snapshotHash,status:t.target};return{type:"transition_run",...N,payloadHash:m,next:{...I,receipts:[...I.receipts,M]}}}function T(t){const e=/^[A-Za-z0-9._:-]{1,128}$/;if(!e.test(t.runId)||!e.test(t.operationId)||!e.test(t.idempotencyKey)||!/^[a-f0-9]{64}$/.test(t.payloadHash)||!Number.isSafeInteger(t.expectedRevision)||t.expectedRevision<0)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT");if(t.payloadHash!==U(t))throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}function L(t,e){const s=e.next,n=t?.events??[],r=t?.progressSnapshots??[],o=t?.receipts??[];if(s.schemaVersion!=="YoloopRunControl/1.0"||s.runId!==e.runId||s.revision!==e.expectedRevision+1||s.events.length!==n.length+1||s.progressSnapshots.length!==r.length+2||s.receipts.length!==o.length+1||d(s.events.slice(0,n.length))!==d(n)||d(s.progressSnapshots.slice(0,r.length))!==d(r)||d(s.receipts.slice(0,o.length))!==d(o))throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(t){if(E.has(t.status))throw new Error("YOLOOP_RUN_TERMINAL");const h=s.progressSnapshots.at(-2)?.boundary,y=t.status===s.status&&(h==="budget"||h==="retry");if(!(ie[t.status]??[]).includes(s.status)&&!y)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(t.startedAt!==s.startedAt||t.deadlineAt!==s.deadlineAt)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(d(t.predecessor)!==d(s.predecessor))throw new Error("YOLOOP_RUN_TRANSITION_INVALID")}else if(s.status!=="created")throw new Error("YOLOOP_RUN_TRANSITION_INVALID");const i=s.progressSnapshots.at(-2),a=s.progressSnapshots.at(-1),c=s.events.at(-1),l=s.receipts.at(-1);if(!i||!a||!c||!l||i.phase!=="before"||a.phase!=="after"||i.operationId!==e.operationId||a.operationId!==e.operationId||c.operationId!==e.operationId||i.runId!==e.runId||a.runId!==e.runId||c.runId!==e.runId||i.revision!==s.revision||a.revision!==s.revision||c.revision!==s.revision||i.parentSnapshotHash!==(t?.latestSnapshotHash??null)||a.parentSnapshotHash!==i.snapshotHash||s.latestSnapshotHash!==a.snapshotHash||l.schemaVersion!=="YoloopRunTransitionReceipt/1.0"||l.runId!==e.runId||l.operationId!==e.operationId||l.idempotencyKey!==e.idempotencyKey||l.payloadHash!==e.payloadHash||l.revision!==s.revision||l.snapshotHash!==s.latestSnapshotHash||l.status!==s.status||Date.parse(s.deadlineAt)<=Date.parse(s.startedAt))throw new Error("YOLOOP_RUN_TRANSITION_INVALID");D(i),D(a),de(c,n.at(-1)?.eventHash??null)}function D(t){const{snapshotHash:e,...s}=t;if(e!==p("p220-yoloop-progress-snapshot-v1",s))throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT")}function de(t,e){const{eventHash:s,...n}=t;if(t.parentEventHash!==e||s!==p("p220-yoloop-run-event-v1",n))throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT")}function le(t,e){return`lease-${ue(`${t}:${e}`)}`}function ue(t){let e=2166136261;for(let s=0;s<t.length;s+=1)e^=t.charCodeAt(s),e=Math.imul(e,16777619)>>>0;return e.toString(36).padStart(7,"0")}function F(t,e){const s=t.leaseExpiresAt??t.ttlExpiresAt,n=Date.parse(s),r=Date.parse(e);return!Number.isFinite(n)||!Number.isFinite(r)?"unknown":n<=r?"expired":"active"}function he(t,e){const s=w(e);return{...t,version:t.version==="1.2"?"1.2":"1.1",lastResetAt:t.lastResetAt??s.lastResetAt,storiesRunToday:t.storiesRunToday??0,iterationsToday:t.iterationsToday??0,iterationsTodayDate:t.iterationsTodayDate??s.iterationsTodayDate,tokensUsedToday:t.tokensUsedToday??0,lastRunAt:t.lastRunAt??null,coolDownUntil:t.coolDownUntil??null,totalRunsAllTime:t.totalRunsAllTime??0,totalStoriesAllTime:t.totalStoriesAllTime??0,currentSession:t.currentSession??null,...t.runControl!==void 0?{runControl:t.runControl}:{},...t.continuationPolicy!==void 0?{continuationPolicy:t.continuationPolicy}:{},...t.continuationSession!==void 0?{continuationSession:t.continuationSession}:{},...t.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:t.pendingContinuationAuthorities}:{},...t.continuationReceipts!==void 0?{continuationReceipts:t.continuationReceipts}:{},...t.continuationRecoveryReceipts!==void 0?{continuationRecoveryReceipts:t.continuationRecoveryReceipts}:{},recovery_history:Array.isArray(t.recovery_history)?K(t.recovery_history):[]}}function K(t){return t.length<=C?t:t.slice(t.length-C)}export{x as YOLOOP_PERSISTENCE_CAPABILITIES,fe as YoloopClientStateStore,U as buildYoloopRunTransitionPayloadHash,Q as buildYoloopSessionRolloverPayloadHash,ee as buildYoloopSuccessorRunPayloadHash,te as buildYoloopSuccessorTransitionPayloadHash,K as capRecoveryHistory,w as createDefaultCounters,we as createDefaultState,Z as fingerprintYoloopSession};
1
+ import{promises as u}from"node:fs";import{createHash as b}from"node:crypto";import{join as _,dirname as V}from"node:path";import{hostname as S}from"node:os";import{setSecureFilePermissions as $,setSecureDirPermissions as q}from"../config/secure-config.js";import{RECOVERY_HISTORY_CAP as C}from"./shared-yoloop-types.js";const B=".neocortex",g="yoloop-state.json",j="yoloop.lock",J="yoloop-state.mutation.lock",P="yoloop-session-rollover.json",A="yoloop-successor-transition.json",z=360*60*1e3,W=3e4,G=200,x=Object.freeze(["rollover-session-v1","successor-transition-v1"]),X={warn:()=>{},info:()=>{},error:()=>{}};function w(t){const e=t.slice(0,10);return{version:"1.1",lastResetAt:t,storiesRunToday:0,iterationsToday:0,iterationsTodayDate:e,tokensUsedToday:0,lastRunAt:null,coolDownUntil:null,totalRunsAllTime:0,totalStoriesAllTime:0,currentSession:null,continuationPolicy:"p205-finite-resume-v2",pendingContinuationAuthorities:[],continuationReceipts:[],continuationRecoveryReceipts:[]}}function we(t){return{lock:null,state:w(t),capabilities:x}}class fe{stateDir;statePath;lockPath;mutationLockPath;rolloverManifestPath;successorTransitionManifestPath;logger;constructor(e,s=X){this.stateDir=_(e,B),this.statePath=_(this.stateDir,g),this.lockPath=_(this.stateDir,j),this.mutationLockPath=_(this.stateDir,J),this.rolloverManifestPath=_(this.stateDir,P),this.successorTransitionManifestPath=_(this.stateDir,A),this.logger=s}async readAll(e){const s=e??new Date().toISOString();await this.cleanupOrphanTempFiles(),await this.recoverSessionRolloverIfPresent(s),await this.recoverSuccessorTransitionIfPresent(s);const[n,r]=await Promise.all([this.readLock(),this.readState(s)]);return{lock:await this.prepareLockForSnapshot(n,s),state:r,capabilities:x}}async applyUpdate(e,s){const n=s??new Date().toISOString();await this.ensureDir(),await this.recoverSessionRolloverIfPresent(n),await this.recoverSuccessorTransitionIfPresent(n);for(const r of e.operations)await this.applyOperation(r,n)}async requestRunTransition(e,s){const n=s??new Date().toISOString();return await this.ensureDir(),this.withMutationLock(()=>this.transitionRun(e,n))}async requestLifecycleAction(e,s={},n){const r=n??new Date().toISOString();return await this.ensureDir(),this.withMutationLock(async()=>{const i=(await this.readState(r)).runControl;if(!i)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");const a=Date.parse(r).toString(36),c=s.operationId??`op-${e}-${a}`,l=s.idempotencyKey??`idem-${e}-${a}`,h=i.receipts.find(O=>O.operationId===c||O.idempotencyKey===l);if(h){if(h.operationId===c&&h.idempotencyKey===l)return h;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}const y=ae(i.status,e);if(!y)throw new Error(E.has(i.status)?"YOLOOP_RUN_TERMINAL":"YOLOOP_RUN_TRANSITION_INVALID");return this.transitionRun(ce({current:i,action:e,target:y,operationId:c,idempotencyKey:l,occurredAt:r}),r)})}async applyOperation(e,s){switch(e.type){case"acquire_lock":await this.acquireLock(e.lock,s);return;case"release_lock":await this.releaseLock(e);return;case"write_state":await this.withMutationLock(async()=>{const n=await this.readState(s),{runControl:r,...o}=e.state;await this.writeState({...o,...n.runControl?{version:"1.2",runControl:n.runControl}:{}})});return;case"advance_epic":await this.advanceEpic(e.nextEpicIndex,e.nextEpicId,s);return;case"no_op":this.logger.info({event:"yoloop_noop"},"yoloop server sent no-op");return;case"reserve_continuation_authority":await this.reserveContinuationAuthority(e.authority,s);return;case"acknowledge_continuation_authority":await this.acknowledgeContinuationAuthority(e.idempotencyKey,e.receipt,s);return;case"repair_continuation_authority":await this.repairContinuationAuthority(e.beforeHash,e.authority,e.receipt,s);return;case"switch_continuation_policy":await this.switchContinuationPolicy(e.policy,e.session,s);return;case"transition_run":await this.requestRunTransition(e,s);return;case"start_successor_run":await this.withMutationLock(()=>this.startSuccessorRun(e,s));return;case"commit_successor_transition":await this.withMutationLock(()=>this.commitSuccessorTransition(e,s));return;case"rollover_session":await this.withMutationLock(()=>this.rolloverSession(e,s));return;case"recovery_attempted":await this.appendRecoveryAttempt(e.attempt,s);return;case"recovery_resolved":await this.resolveRecoveryAttempt(e.attempt_id,e.outcome,s);return;default:{const n=e;throw this.logger.error({event:"yoloop_unknown_op",op:n},"unknown yoloop operation rejected"),new Error("YOLOOP_STATE_OPERATION_UNSUPPORTED")}}}async reserveContinuationAuthority(e,s){const n=await this.readState(s),r=n.pendingContinuationAuthorities??[],o=typeof e.idempotencyKey=="string"?e.idempotencyKey:"";if(!o)throw new Error("YOLOOP_CONTINUATION_AUTHORITY_INVALID");const i=r.find(a=>a.idempotencyKey===o);if(i){if(d(i)===d(e))return;throw new Error("YOLOOP_CONTINUATION_AUTHORITY_CONFLICT")}if(r.length>=64)throw new Error("YOLOOP_PENDING_AUTHORITY_CAPACITY_EXCEEDED");await this.writeState({...n,pendingContinuationAuthorities:[...r,e]})}async acknowledgeContinuationAuthority(e,s,n){const r=await this.readState(n),o=r.pendingContinuationAuthorities??[],i=o.findIndex(h=>h.idempotencyKey===e);if(i<0)throw new Error("YOLOOP_CONTINUATION_RECEIPT_ORPHAN");const a=o[i];if(s.idempotencyKey!==e||s.sessionId!==a.sessionId||JSON.stringify(s.storyIds)!==JSON.stringify(a.storyIds))throw new Error("YOLOOP_CONTINUATION_RECEIPT_MISMATCH");const c=o.filter((h,y)=>y!==i),l=r.continuationReceipts??[];await this.writeState({...r,pendingContinuationAuthorities:c,continuationReceipts:[...l.slice(-63),s]})}async repairContinuationAuthority(e,s,n,r){const o=await this.readState(r),i=o.pendingContinuationAuthorities??[],a=o.continuationRecoveryReceipts??[],c=typeof n.idempotencyKey=="string"?n.idempotencyKey:"",l=a.find(O=>O.idempotencyKey===c);if(l){if(d(l)===d(n))return;throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_CONFLICT")}if(i.length!==1||p("p208-legacy-authority-before-v1",i[0])!==e)throw new Error("YOLOOP_CONTINUATION_RECOVERY_BEFORE_MISMATCH");const h=p("p208-legacy-authority-after-v1",s);if(n.beforeHash!==e||n.afterHash!==h)throw new Error("YOLOOP_CONTINUATION_RECOVERY_HASH_MISMATCH");const y={...n};if(delete y.idempotencyKey,!c||p("p208-legacy-authority-recovery-receipt-v1",y)!==c)throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_INVALID");await this.writeState({...o,pendingContinuationAuthorities:[s],continuationRecoveryReceipts:[...a.slice(-63),n]})}async switchContinuationPolicy(e,s,n){const r=await this.readState(n);if(r.currentSession||(r.pendingContinuationAuthorities?.length??0)>0)throw new Error("YOLOOP_POLICY_SWITCH_ACTIVE");try{await u.copyFile(this.statePath,`${this.statePath}.policy-backup`)}catch(o){if(o.code!=="ENOENT")throw o}await this.writeState({...r,continuationPolicy:e,continuationSession:s})}async appendRecoveryAttempt(e,s){const n=await this.readState(s),r=n.recovery_history??[];if(r.some(c=>c.attempted_at===e.attempted_at&&c.story_id===e.story_id)){this.logger.warn({event:"recovery_attempted_duplicate",story_id:e.story_id},"duplicate recovery_attempted op; skipping");return}const i=K([...r,e]),a={...n,recovery_history:i};await this.writeState(a),this.logger.info({event:"recovery_attempted_persisted",story_id:e.story_id,history_size:i.length},"recovery attempt persisted")}async resolveRecoveryAttempt(e,s,n){const r=await this.readState(n),o=r.recovery_history??[],i=o.findIndex(h=>h.recovery_step_id_tokenized===e);if(i===-1){this.logger.warn({event:"recovery_resolve_orphan",attempt_id:e},"recovery_resolved op had no matching attempt; skipping");return}const a=o[i];if(a.outcome===s&&a.finished_at){this.logger.info({event:"recovery_resolved_duplicate",attempt_id:e,outcome:s},"duplicate recovery_resolved op; skipping");return}const c=o.slice();c[i]={...a,outcome:s,finished_at:n};const l={...r,recovery_history:c};await this.writeState(l),this.logger.info({event:"recovery_resolved_persisted",attempt_id:e,outcome:s},"recovery attempt resolved")}async acquireLock(e,s){const n={...e,pid:process.pid,hostname:S(),ownerKind:"client",startedAt:e.startedAt||s,leaseId:e.leaseId??le(e.sessionId,s),leaseState:"active",leaseAcquiredAt:e.leaseAcquiredAt??s,leaseDurationMs:e.leaseDurationMs??z,leaseExpiresAt:e.leaseExpiresAt??e.ttlExpiresAt},r=JSON.stringify(n,null,2);try{const o=await u.open(this.lockPath,"wx");try{await o.writeFile(r,"utf8")}finally{await o.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired",pid:n.pid,sessionId:n.sessionId},"yoloop lock acquired");return}catch(o){const i=o;if(i.code!=="EEXIST")throw this.logger.error({event:"yoloop_lock_acquire_error",err:i.message},"failed to acquire yoloop lock"),o;const a=await this.readLock();if(a&&this.isSameClientSessionLock(a,n)){this.logger.info({event:"yoloop_lock_acquire_duplicate",sessionId:n.sessionId},"duplicate acquire_lock for current yoloop session; treating as no-op");return}if(!await this.maybeRecoverOrphanLock(s))throw this.logger.warn({event:"yoloop_lock_held"},"yoloop lock held by another live process"),o;const l=await u.open(this.lockPath,"wx");try{await l.writeFile(r,"utf8")}finally{await l.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired_after_recovery",pid:n.pid},"yoloop lock acquired after orphan recovery")}}async releaseLock(e){const s=await this.readState(new Date().toISOString()),n=await this.readLock();if(s.runControl){if(!e.expectedRunId||!e.expectedSessionId||!e.expectedLeaseId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(e.expectedRunId!==s.runControl.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(!n||e.expectedSessionId!==n.sessionId||e.expectedLeaseId!==n.leaseId)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}try{await u.unlink(this.lockPath),this.logger.info({event:"yoloop_lock_released"},"yoloop lock released")}catch(r){const o=r;if(o.code==="ENOENT"){this.logger.info({event:"yoloop_lock_release_noop"},"yoloop lock release noop (file absent)");return}this.logger.warn({event:"yoloop_lock_release_error",err:o.message},"unexpected error releasing yoloop lock")}}async readLock(){try{const e=await u.readFile(this.lockPath,"utf8"),s=JSON.parse(e),n=s.currentEpicId??s.epicId??"";return{pid:s.pid??0,hostname:s.hostname??"",...s.ownerKind?{ownerKind:s.ownerKind}:{},...s.ownerPidState?{ownerPidState:s.ownerPidState}:{},...typeof s.leaseId=="string"?{leaseId:s.leaseId}:{},...s.leaseState?{leaseState:s.leaseState}:{},...typeof s.leaseAcquiredAt=="string"?{leaseAcquiredAt:s.leaseAcquiredAt}:{},...typeof s.leaseDurationMs=="number"?{leaseDurationMs:s.leaseDurationMs}:{},...typeof s.leaseExpiresAt=="string"?{leaseExpiresAt:s.leaseExpiresAt}:{},startedAt:s.startedAt??"",sessionId:s.sessionId??"",mode:s.mode??"epic-id",inputPath:s.inputPath??"",epicQueue:s.epicQueue??(n?[n]:[]),currentEpicIndex:s.currentEpicIndex??0,currentEpicId:n,ttlExpiresAt:s.ttlExpiresAt??"",epicId:s.epicId??n}}catch{return null}}async readState(e){try{const s=await u.readFile(this.statePath,"utf8"),n=JSON.parse(s);return he(n,e)}catch(s){const n=s;return n.code!=="ENOENT"&&this.logger.warn({event:"yoloop_state_read_error",err:n.message},"yoloop state file unreadable; using defaults"),w(e)}}async writeState(e){const s=`${this.statePath}.tmp.${process.pid}.${Date.now()}`,n=JSON.stringify(e,null,2);try{const r=await u.open(s,"w",384);try{await r.writeFile(n,"utf8"),await r.sync()}finally{await r.close()}this.trySetFilePerms(s),await u.rename(s,this.statePath),await this.syncStateDirectory()}catch(r){const o=r;this.logger.error({event:"yoloop_state_write_error",err:o.message},"failed to write yoloop state");try{await u.unlink(s)}catch{}throw r}}async rolloverSession(e,s){v(e);const n=await this.readRolloverManifest();if(n){if(oe(n.operation,e)){if(n.operation.payloadHash!==e.payloadHash)throw new Error("YOLOOP_SESSION_ROLLOVER_CONFLICT");if(n.phase==="applied")return;await this.completePreparedSessionRollover(n.operation,s);return}else if(n.phase==="prepared")throw new Error("YOLOOP_SESSION_ROLLOVER_CONFLICT")}const r=await this.readState(s),o=await this.readLock();H(e,r,o),await this.writeRolloverManifest({schemaVersion:"YoloopSessionRolloverManifest/1.0",phase:"prepared",operation:e}),await this.completePreparedSessionRollover(e,s)}async completePreparedSessionRollover(e,s){const n=await this.readState(s);let r=d(n)===d(e.next);if(!r){const i=await this.readLock();H(e,n,i),await this.writeState(e.next),r=!0}const o=await this.readLock();if(e.expectedSessionId===null||e.expectedLeaseId===null){if(e.expectedSessionId!==null||e.expectedLeaseId!==null||o)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT")}else if(o){if(o.sessionId!==e.expectedSessionId||o.leaseId!==e.expectedLeaseId)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT");await u.unlink(this.lockPath),await this.syncStateDirectory()}else if(!r)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT");await this.writeRolloverManifest({schemaVersion:"YoloopSessionRolloverManifest/1.0",phase:"applied",operation:e}),this.logger.info({event:"yoloop_session_rollover_applied",runId:e.runId,operationId:e.operationId,coolDownUntil:e.coolDownUntil},"yoloop per-run-cap session rollover applied")}async recoverSessionRolloverIfPresent(e){try{await u.access(this.rolloverManifestPath)}catch(s){if(s.code==="ENOENT")return;throw s}await this.ensureDir(),await this.withMutationLock(async()=>{const s=await this.readRolloverManifest();s?.phase==="prepared"&&await this.completePreparedSessionRollover(s.operation,e)})}async readRolloverManifest(){try{const e=JSON.parse(await u.readFile(this.rolloverManifestPath,"utf8"));if(e.schemaVersion!=="YoloopSessionRolloverManifest/1.0"||e.phase!=="prepared"&&e.phase!=="applied"||!e.operation||e.operation.type!=="rollover_session")throw new Error("YOLOOP_SESSION_ROLLOVER_MANIFEST_CORRUPT");return v(e.operation),e}catch(e){if(e.code==="ENOENT")return null;throw e.message==="YOLOOP_SESSION_ROLLOVER_MANIFEST_CORRUPT"?e:new Error("YOLOOP_SESSION_ROLLOVER_MANIFEST_CORRUPT")}}async writeRolloverManifest(e){const s=`${this.rolloverManifestPath}.tmp.${process.pid}.${Date.now()}`,n=await u.open(s,"w",384);try{await n.writeFile(JSON.stringify(e,null,2),"utf8"),await n.sync()}finally{await n.close()}this.trySetFilePerms(s),await u.rename(s,this.rolloverManifestPath),await this.syncStateDirectory()}async commitSuccessorTransition(e,s){f(e);const n=await this.readSuccessorTransitionManifest();if(n){if(se(n.operation,e)){if(n.operation.payloadHash!==e.payloadHash)throw new Error("YOLOOP_SUCCESSOR_TRANSITION_CONFLICT");if(n.phase==="applied")return;await this.completePreparedSuccessorTransition(n.operation,s);return}if(n.phase==="prepared")throw new Error("YOLOOP_SUCCESSOR_TRANSITION_CONFLICT")}const r=await this.readState(s);if(R(e,r)){await this.writeSuccessorTransitionManifest({schemaVersion:"YoloopSuccessorTransitionManifest/1.0",phase:"applied",operation:e});return}k(e,r),await this.writeSuccessorTransitionManifest({schemaVersion:"YoloopSuccessorTransitionManifest/1.0",phase:"prepared",operation:e}),await this.completePreparedSuccessorTransition(e,s)}async completePreparedSuccessorTransition(e,s){let n=await this.readState(s);if(!R(e,n)){d(n.runControl)===d(e.successor.next)||(await this.startSuccessorRun(e.successor,s),n=await this.readState(s));const o=re(e,n);await this.writeState(o),n=await this.readState(s)}if(!R(e,n))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_STATE_CONFLICT");await this.writeSuccessorTransitionManifest({schemaVersion:"YoloopSuccessorTransitionManifest/1.0",phase:"applied",operation:e}),this.logger.info({event:"yoloop_successor_transition_applied",runId:e.successor.runId,operationId:e.operationId},"yoloop successor transition applied")}async recoverSuccessorTransitionIfPresent(e){try{await u.access(this.successorTransitionManifestPath)}catch(s){if(s.code==="ENOENT")return;throw s}await this.ensureDir(),await this.withMutationLock(async()=>{const s=await this.readSuccessorTransitionManifest();s?.phase==="prepared"&&await this.completePreparedSuccessorTransition(s.operation,e)})}async readSuccessorTransitionManifest(){try{const e=JSON.parse(await u.readFile(this.successorTransitionManifestPath,"utf8"));if(e.schemaVersion!=="YoloopSuccessorTransitionManifest/1.0"||e.phase!=="prepared"&&e.phase!=="applied"||!e.operation||e.operation.type!=="commit_successor_transition")throw new Error("YOLOOP_SUCCESSOR_TRANSITION_MANIFEST_CORRUPT");return f(e.operation),e}catch(e){if(e.code==="ENOENT")return null;throw e.message==="YOLOOP_SUCCESSOR_TRANSITION_MANIFEST_CORRUPT"?e:new Error("YOLOOP_SUCCESSOR_TRANSITION_MANIFEST_CORRUPT")}}async writeSuccessorTransitionManifest(e){const s=`${this.successorTransitionManifestPath}.tmp.${process.pid}.${Date.now()}`,n=await u.open(s,"w",384);try{await n.writeFile(JSON.stringify(e,null,2),"utf8"),await n.sync()}finally{await n.close()}this.trySetFilePerms(s),await u.rename(s,this.successorTransitionManifestPath),await this.syncStateDirectory()}async advanceEpic(e,s,n){const r=await this.readState(n);if(!r.currentSession){this.logger.warn({event:"yoloop_advance_no_session",nextEpicId:s},"advance_epic with no active session; skipping");return}const o={...r,currentSession:{...r.currentSession,currentEpicIndex:e}};await this.writeState(o),this.logger.info({event:"yoloop_epic_advance",nextEpicId:s,nextIndex:e},"yoloop advanced to next epic")}async ensureDir(){try{await u.mkdir(this.stateDir,{recursive:!0}),this.trySetDirPerms(this.stateDir)}catch(e){const s=e;throw this.logger.error({event:"yoloop_mkdir_error",err:s.message,path:this.stateDir},"failed to ensure .neocortex/ directory"),e}}async syncStateDirectory(){try{const e=await u.open(this.stateDir,"r");try{await e.sync()}finally{await e.close()}}catch(e){const s=e.code;if(s!=="EINVAL"&&s!=="EISDIR"&&s!=="ENOTSUP"&&s!=="EPERM")throw e}}async withMutationLock(e){let s=null;for(let n=0;n<G;n+=1)try{s=await u.open(this.mutationLockPath,"wx",384),await s.writeFile(JSON.stringify({pid:process.pid,acquiredAt:new Date().toISOString()})),await s.sync();break}catch(r){if(r.code!=="EEXIST")throw r;try{const o=await u.stat(this.mutationLockPath);if(Date.now()-o.mtimeMs>W){await u.unlink(this.mutationLockPath);continue}}catch{continue}await new Promise(o=>setTimeout(o,5))}if(!s)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");try{return await e()}finally{await s.close().catch(()=>{}),await u.unlink(this.mutationLockPath).catch(()=>{})}}async transitionRun(e,s){T(e);const n=await this.readState(s),r=n.runControl,o=r?.receipts.find(l=>l.operationId===e.operationId||l.idempotencyKey===e.idempotencyKey);if(o){if(o.operationId===e.operationId&&o.idempotencyKey===e.idempotencyKey&&o.payloadHash===e.payloadHash)return o;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}if(r&&r.runId!==e.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if((r?.revision??0)!==e.expectedRevision)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if((r?.latestSnapshotHash??null)!==e.expectedSnapshotHash)throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT");L(r,e);const c=e.next.receipts.at(-1);if(!c)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT");return await this.writeState({...n,version:"1.2",runControl:e.next}),this.logger.info({event:"yoloop_run_transition_committed",runId:e.runId,operationId:e.operationId,revision:c.revision,status:c.status},"yoloop run transition committed"),c}async startSuccessorRun(e,s){Y(e);const n=await this.readState(s),r=n.runControl,o={schemaVersion:"YoloopRunPredecessor/1.0",runId:e.previousRunId,revision:e.expectedRevision,snapshotHash:e.expectedSnapshotHash,successorOperationId:e.operationId,publicSafe:!0},i=r?.receipts.find(l=>l.operationId===e.operationId||l.idempotencyKey===e.idempotencyKey);if(i){if(i.operationId===e.operationId&&i.idempotencyKey===e.idempotencyKey&&e.next.receipts.at(-1)?.payloadHash===i.payloadHash&&d(r?.predecessor)===d(o))return i;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}if(!r||r.runId!==e.previousRunId||!["exhausted","success"].includes(r.status)||r.revision!==e.expectedRevision||r.latestSnapshotHash!==e.expectedSnapshotHash||n.currentSession!==null)throw new Error("YOLOOP_RUN_SUCCESSOR_CONFLICT");const a=e.next.receipts.at(-1);if(!a||d(e.next.predecessor)!==d(o))throw new Error("YOLOOP_RUN_SUCCESSOR_CONFLICT");const c={type:"transition_run",runId:e.runId,operationId:e.operationId,idempotencyKey:e.idempotencyKey,expectedRevision:0,expectedSnapshotHash:null,payloadHash:a.payloadHash,next:e.next};return T(c),L(void 0,c),await this.writeState({...n,version:"1.2",runControl:e.next}),a}trySetFilePerms(e){try{$(e)}catch{}}trySetDirPerms(e){try{q(e)}catch{}}async prepareLockForSnapshot(e,s){if(!e)return null;if(e.ownerKind!=="client")return e;const n=F(e,s),r={...e,leaseState:n};if(n==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"lease_expired"},"expired client-owned yoloop lock lease recovered before snapshot echo"),null;if(!r.pid||r.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"invalid_pid"},"invalid PID in client-owned yoloop lock; recovered"),null;if(r.hostname&&r.hostname!==S())return{...r,ownerPidState:"unknown"};try{return process.kill(r.pid,0),{...r,ownerPidState:"live"}}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"dead_pid"},"stale client-owned yoloop lock recovered before snapshot echo"),null}}async maybeRecoverOrphanLock(e){const s=await this.readLock();if(!s)return!0;if(F(s,e)==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"lease_expired"},"expired yoloop lock lease recovered"),!0;if(!s.pid||s.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"invalid_pid"},"invalid PID in lock file; recovered"),!0;if(s.hostname&&s.hostname!==S())return this.logger.warn({event:"yoloop_lock_remote_host",ownerKind:s.ownerKind??"server",ownerPidState:"unknown"},"lock held by a remote host; honoring"),!1;try{return process.kill(s.pid,0),!1}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"dead_pid"},"orphan yoloop lock recovered"),!0}}async deleteLockBestEffort(){try{await u.unlink(this.lockPath)}catch{}}isSameClientSessionLock(e,s){return e.ownerKind==="client"&&e.sessionId===s.sessionId&&e.pid===process.pid&&e.hostname===S()}async cleanupOrphanTempFiles(){try{const e=V(this.statePath),s=await u.readdir(e),n=[`${g}.tmp.`,`${P}.tmp.`,`${A}.tmp.`],r=s.filter(o=>n.some(i=>o.startsWith(i)));for(const o of r)try{await u.unlink(_(e,o))}catch{}}catch{}}}function d(t){return t===null||typeof t!="object"?JSON.stringify(t):Array.isArray(t)?`[${t.map(d).join(",")}]`:`{${Object.entries(t).filter(([,e])=>e!==void 0).sort(([e],[s])=>e.localeCompare(s)).map(([e,s])=>`${JSON.stringify(e)}:${d(s)}`).join(",")}}`}function p(t,e){return b("sha256").update(`${t}\0${d(e)}`).digest("hex")}function Z(t){return p("p9390812898200217364-yoloop-session-v1",t)}function Q(t){return p("p9390812898200217364-yoloop-rollover-v1",t)}function ee(t){return p("p9390812898200217364-yoloop-successor-run-v1",t)}function te(t){return p("p9046281030721413419-yoloop-successor-transition-v1",t)}function se(t,e){return t.operationId===e.operationId&&t.idempotencyKey===e.idempotencyKey}function f(t){const e=/^[A-Za-z0-9._:-]{1,128}$/;Y(t.successor);const s={operationId:t.operationId,idempotencyKey:t.idempotencyKey,successor:t.successor,state:t.state,...t.acknowledgement?{acknowledgement:t.acknowledgement}:{},...t.reservation?{reservation:t.reservation}:{}},n=t.acknowledgement?.idempotencyKey,r=t.acknowledgement?.receipt.idempotencyKey,o=t.reservation?.authority.idempotencyKey;if(t.successor.type!=="start_successor_run"||!e.test(t.operationId)||!e.test(t.idempotencyKey)||t.operationId!==t.successor.operationId||t.idempotencyKey!==t.successor.idempotencyKey||!/^[a-f0-9]{64}$/.test(t.payloadHash)||t.payloadHash!==te(s)||Buffer.byteLength(d(s),"utf8")>1048576||t.acknowledgement!==void 0&&(t.acknowledgement.type!=="acknowledge_continuation_authority"||typeof n!="string"||!e.test(n)||r!==n)||t.reservation!==void 0&&(t.reservation.type!=="reserve_continuation_authority"||typeof o!="string"||!e.test(o))||n!==void 0&&o===n)throw new Error("YOLOOP_SUCCESSOR_TRANSITION_CONFLICT")}function k(t,e){if(f(t),!(d(e.runControl)===d(t.successor.next))){const n=e.runControl;if(!n||n.runId!==t.successor.previousRunId||!["exhausted","success"].includes(n.status)||n.revision!==t.successor.expectedRevision||n.latestSnapshotHash!==t.successor.expectedSnapshotHash||e.currentSession!==null)throw new Error("YOLOOP_SUCCESSOR_TRANSITION_STATE_CONFLICT")}ne(t,e)}function ne(t,e){const s=e.pendingContinuationAuthorities??[],n=e.continuationReceipts??[],r=t.acknowledgement;if(r){const l=s.find(O=>O.idempotencyKey===r.idempotencyKey);if(!l||r.receipt.sessionId!==l.sessionId||d(r.receipt.storyIds)!==d(l.storyIds))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT");const h=s.filter(O=>O.idempotencyKey!==r.idempotencyKey),y=[...n.slice(-63),r.receipt];if(d(t.state.pendingContinuationAuthorities??[])!==d(h)||d(t.state.continuationReceipts??[])!==d(y))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT")}else if(d(t.state.pendingContinuationAuthorities??[])!==d(s)||d(t.state.continuationReceipts??[])!==d(n))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT");const o=t.reservation?.authority;if(!o)return;const i=String(o.idempotencyKey??""),a=t.state.pendingContinuationAuthorities??[],c=a.find(l=>l.idempotencyKey===i);if(c&&d(c)!==d(o))throw new Error("YOLOOP_SUCCESSOR_TRANSITION_AUTHORITY_CONFLICT");if(!c&&a.length>=64)throw new Error("YOLOOP_PENDING_AUTHORITY_CAPACITY_EXCEEDED")}function re(t,e){k(t,e);const s=t.reservation?.authority,n=[...t.state.pendingContinuationAuthorities??[]];return s&&!n.some(r=>r.idempotencyKey===s.idempotencyKey)&&n.push(s),{...t.state,version:"1.2",runControl:t.successor.next,...s||t.state.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:n}:{}}}function R(t,e){const s=t.reservation?.authority,n=[...t.state.pendingContinuationAuthorities??[]];s&&!n.some(o=>o.idempotencyKey===s.idempotencyKey)&&n.push(s);const r={...t.state,version:"1.2",runControl:t.successor.next,...s||t.state.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:n}:{}};return d(e)===d(r)}function oe(t,e){return t.operationId===e.operationId&&t.idempotencyKey===e.idempotencyKey}function v(t){const e=/^[A-Za-z0-9._:-]{1,128}$/,s={runId:t.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.expectedRevision,expectedSnapshotHash:t.expectedSnapshotHash,expectedSessionId:t.expectedSessionId,expectedSessionFingerprint:t.expectedSessionFingerprint,expectedLeaseId:t.expectedLeaseId,coolDownUntil:t.coolDownUntil,reason:t.reason,exhaustedRun:t.exhaustedRun,next:t.next};if(!e.test(t.runId)||!e.test(t.operationId)||!e.test(t.idempotencyKey)||!/^[a-f0-9]{64}$/.test(t.expectedSessionFingerprint)||!/^[a-f0-9]{64}$/.test(t.payloadHash)||!Number.isSafeInteger(t.expectedRevision)||t.expectedRevision<0||!Number.isFinite(Date.parse(t.coolDownUntil))||!["YOLOOP_PER_RUN_CAP_REACHED","YOLOOP_SESSION_TIMEOUT"].includes(t.reason)||t.payloadHash!==Q(s)||t.receipt.schemaVersion!=="YoloopSessionRolloverReceipt/1.0"||t.receipt.runId!==t.runId||t.receipt.operationId!==t.operationId||t.receipt.idempotencyKey!==t.idempotencyKey||t.receipt.payloadHash!==t.payloadHash||t.receipt.coolDownUntil!==t.coolDownUntil||t.receipt.revision!==(t.exhaustedRun?.revision??0)||t.receipt.snapshotHash!==(t.exhaustedRun?.latestSnapshotHash??null)||t.receipt.status!=="exhausted"||t.receipt.publicSafe!==!0)throw new Error("YOLOOP_SESSION_ROLLOVER_CONFLICT");if(t.expectedSessionId===null!=(t.expectedLeaseId===null)||t.expectedSessionId!==null&&!e.test(t.expectedSessionId)||t.expectedLeaseId!==null&&!e.test(t.expectedLeaseId))throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT")}function Y(t){const e=/^[A-Za-z0-9._:-]{1,128}$/,s={runId:t.runId,previousRunId:t.previousRunId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.expectedRevision,expectedSnapshotHash:t.expectedSnapshotHash,next:t.next};if(!e.test(t.runId)||!e.test(t.previousRunId)||t.runId===t.previousRunId||!e.test(t.operationId)||!e.test(t.idempotencyKey)||!/^[a-f0-9]{64}$/.test(t.payloadHash)||!Number.isSafeInteger(t.expectedRevision)||t.expectedRevision<1||t.payloadHash!==ee(s))throw new Error("YOLOOP_RUN_SUCCESSOR_CONFLICT")}function H(t,e,s){v(t);const n=e.currentSession;if(!n||Z(n)!==t.expectedSessionFingerprint)throw new Error("YOLOOP_SESSION_ROLLOVER_SESSION_CONFLICT");const r=e.runControl;if((r?.revision??0)!==t.expectedRevision||(r?.latestSnapshotHash??null)!==t.expectedSnapshotHash||r&&r.runId!==t.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(t.expectedSessionId===null||t.expectedLeaseId===null){if(s)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT")}else if(!s||s.sessionId!==t.expectedSessionId||s.leaseId!==t.expectedLeaseId)throw new Error("YOLOOP_SESSION_ROLLOVER_LOCK_CONFLICT");const o=t.exhaustedRun;if(r)if(r.status==="exhausted"){if(d(o)!==d(r))throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT")}else{if(!o||o.status!=="exhausted")throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT");const a=o.receipts.at(-1);if(!a)throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT");const c={type:"transition_run",runId:r.runId,operationId:a.operationId,idempotencyKey:a.idempotencyKey,expectedRevision:r.revision,expectedSnapshotHash:r.latestSnapshotHash,payloadHash:a.payloadHash,next:o};T(c),L(r,c)}else{const a=o?.receipts[0],c=o?.receipts[1];if(o&&(o.runId!==t.runId||o.status!=="exhausted"||o.revision!==2||o.events.length!==2||o.progressSnapshots.length!==4||o.receipts.length!==2||a?.runId!==t.runId||a.revision!==1||a.status!=="created"||c?.runId!==t.runId||c.revision!==2||c.status!=="exhausted"||c.operationId!==`${t.operationId}:run`||c.idempotencyKey!==`${t.idempotencyKey}:run`||o.latestSnapshotHash!==c.snapshotHash||o.predecessor!==void 0))throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT")}const i={...e,version:"1.2",coolDownUntil:t.coolDownUntil,currentSession:null,...o?{runControl:o}:{}};if(d(i)!==d(t.next))throw new Error("YOLOOP_SESSION_ROLLOVER_STATE_CONFLICT")}function U(t){const{receipts:e,...s}=t.next;return p("p220-yoloop-run-transition-payload-v1",{runId:t.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.expectedRevision,expectedSnapshotHash:t.expectedSnapshotHash,next:s})}const E=new Set(["success","partial","blocked","failure","exhausted","cancelled"]),ie={created:["running","cancelling","exhausted"],running:["pausing","cancelling","success","partial","blocked","failure","exhausted"],pausing:["paused","cancelling","failure","exhausted"],paused:["resuming","cancelling","exhausted"],resuming:["running","cancelling","failure","exhausted"],cancelling:["cancelled","failure","exhausted"]};function ae(t,e){return e==="pause"?t==="running"?"pausing":null:e==="resume"?t==="paused"?"resuming":null:E.has(t)||t==="cancelling"?null:"cancelling"}function ce(t){const e=t.current.revision+1,s=t.action==="cancel"?["YOLOOP_RUN_CANCEL_REQUESTED"]:[],n={schemaVersion:"YoloopOperationalBudgetSummary/1.0",limits:t.current.operationalBudget.limits,usage:t.current.usage,exhaustedDimension:null},r={schemaVersion:"ProgressSnapshot/1.0",runId:t.current.runId,sequence:t.current.progressSnapshots.length+1,revision:e,operationId:t.operationId,boundary:t.action,phase:"before",status:t.current.status,occurredAt:t.occurredAt,parentSnapshotHash:t.current.latestSnapshotHash,budget:n,reasonCodes:s,publicSafe:!0},o=p("p220-yoloop-progress-snapshot-id-v1",{runId:r.runId,operationId:r.operationId,sequence:r.sequence,phase:r.phase}),i={...r,snapshotId:o,snapshotHash:p("p220-yoloop-progress-snapshot-v1",{...r,snapshotId:o})},a={...r,sequence:i.sequence+1,phase:"after",status:t.target,parentSnapshotHash:i.snapshotHash},c=p("p220-yoloop-progress-snapshot-id-v1",{runId:a.runId,operationId:a.operationId,sequence:a.sequence,phase:a.phase}),l={...a,snapshotId:c,snapshotHash:p("p220-yoloop-progress-snapshot-v1",{...a,snapshotId:c})},h={schemaVersion:"YoloopRunEvent/1.0",runId:t.current.runId,operationId:t.operationId,kind:"intent",sequence:t.current.events.length+1,revision:e,occurredAt:t.occurredAt,fromStatus:t.current.status,toStatus:t.target,reasonCodes:s,budgetDimension:null,parentEventHash:t.current.events.at(-1)?.eventHash??null,publicSafe:!0},y=p("p220-yoloop-run-event-id-v1",{runId:h.runId,operationId:h.operationId,sequence:h.sequence}),O={...h,eventId:y,eventHash:p("p220-yoloop-run-event-v1",{...h,eventId:y})},I={...t.current,revision:e,status:t.target,events:[...t.current.events,O],progressSnapshots:[...t.current.progressSnapshots,i,l],latestSnapshotHash:l.snapshotHash,receipts:[...t.current.receipts]},N={runId:t.current.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,expectedRevision:t.current.revision,expectedSnapshotHash:t.current.latestSnapshotHash,next:I},m=U(N),M={schemaVersion:"YoloopRunTransitionReceipt/1.0",runId:t.current.runId,operationId:t.operationId,idempotencyKey:t.idempotencyKey,payloadHash:m,revision:e,snapshotHash:l.snapshotHash,status:t.target};return{type:"transition_run",...N,payloadHash:m,next:{...I,receipts:[...I.receipts,M]}}}function T(t){const e=/^[A-Za-z0-9._:-]{1,128}$/;if(!e.test(t.runId)||!e.test(t.operationId)||!e.test(t.idempotencyKey)||!/^[a-f0-9]{64}$/.test(t.payloadHash)||!Number.isSafeInteger(t.expectedRevision)||t.expectedRevision<0)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT");if(t.payloadHash!==U(t))throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}function L(t,e){const s=e.next,n=t?.events??[],r=t?.progressSnapshots??[],o=t?.receipts??[];if(s.schemaVersion!=="YoloopRunControl/1.0"||s.runId!==e.runId||s.revision!==e.expectedRevision+1||s.events.length!==n.length+1||s.progressSnapshots.length!==r.length+2||s.receipts.length!==o.length+1||d(s.events.slice(0,n.length))!==d(n)||d(s.progressSnapshots.slice(0,r.length))!==d(r)||d(s.receipts.slice(0,o.length))!==d(o))throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(t){if(E.has(t.status))throw new Error("YOLOOP_RUN_TERMINAL");const h=s.progressSnapshots.at(-2)?.boundary,y=t.status===s.status&&(h==="budget"||h==="retry");if(!(ie[t.status]??[]).includes(s.status)&&!y)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(t.startedAt!==s.startedAt||t.deadlineAt!==s.deadlineAt)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(d(t.predecessor)!==d(s.predecessor))throw new Error("YOLOOP_RUN_TRANSITION_INVALID")}else if(s.status!=="created")throw new Error("YOLOOP_RUN_TRANSITION_INVALID");const i=s.progressSnapshots.at(-2),a=s.progressSnapshots.at(-1),c=s.events.at(-1),l=s.receipts.at(-1);if(!i||!a||!c||!l||i.phase!=="before"||a.phase!=="after"||i.operationId!==e.operationId||a.operationId!==e.operationId||c.operationId!==e.operationId||i.runId!==e.runId||a.runId!==e.runId||c.runId!==e.runId||i.revision!==s.revision||a.revision!==s.revision||c.revision!==s.revision||i.parentSnapshotHash!==(t?.latestSnapshotHash??null)||a.parentSnapshotHash!==i.snapshotHash||s.latestSnapshotHash!==a.snapshotHash||l.schemaVersion!=="YoloopRunTransitionReceipt/1.0"||l.runId!==e.runId||l.operationId!==e.operationId||l.idempotencyKey!==e.idempotencyKey||l.payloadHash!==e.payloadHash||l.revision!==s.revision||l.snapshotHash!==s.latestSnapshotHash||l.status!==s.status||Date.parse(s.deadlineAt)<=Date.parse(s.startedAt))throw new Error("YOLOOP_RUN_TRANSITION_INVALID");D(i),D(a),de(c,n.at(-1)?.eventHash??null)}function D(t){const{snapshotHash:e,...s}=t;if(e!==p("p220-yoloop-progress-snapshot-v1",s))throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT")}function de(t,e){const{eventHash:s,...n}=t;if(t.parentEventHash!==e||s!==p("p220-yoloop-run-event-v1",n))throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT")}function le(t,e){return`lease-${ue(`${t}:${e}`)}`}function ue(t){let e=2166136261;for(let s=0;s<t.length;s+=1)e^=t.charCodeAt(s),e=Math.imul(e,16777619)>>>0;return e.toString(36).padStart(7,"0")}function F(t,e){const s=t.leaseExpiresAt??t.ttlExpiresAt,n=Date.parse(s),r=Date.parse(e);return!Number.isFinite(n)||!Number.isFinite(r)?"unknown":n<=r?"expired":"active"}function he(t,e){const s=w(e);return{...t,version:t.version==="1.2"?"1.2":"1.1",lastResetAt:t.lastResetAt??s.lastResetAt,storiesRunToday:t.storiesRunToday??0,iterationsToday:t.iterationsToday??0,iterationsTodayDate:t.iterationsTodayDate??s.iterationsTodayDate,tokensUsedToday:t.tokensUsedToday??0,lastRunAt:t.lastRunAt??null,coolDownUntil:t.coolDownUntil??null,totalRunsAllTime:t.totalRunsAllTime??0,totalStoriesAllTime:t.totalStoriesAllTime??0,currentSession:t.currentSession??null,...t.runControl!==void 0?{runControl:t.runControl}:{},...t.continuationPolicy!==void 0?{continuationPolicy:t.continuationPolicy}:{},...t.continuationSession!==void 0?{continuationSession:t.continuationSession}:{},...t.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:t.pendingContinuationAuthorities}:{},...t.continuationReceipts!==void 0?{continuationReceipts:t.continuationReceipts}:{},...t.continuationRecoveryReceipts!==void 0?{continuationRecoveryReceipts:t.continuationRecoveryReceipts}:{},recovery_history:Array.isArray(t.recovery_history)?K(t.recovery_history):[]}}function K(t){return t.length<=C?t:t.slice(t.length-C)}export{x as YOLOOP_PERSISTENCE_CAPABILITIES,fe as YoloopClientStateStore,U as buildYoloopRunTransitionPayloadHash,Q as buildYoloopSessionRolloverPayloadHash,ee as buildYoloopSuccessorRunPayloadHash,te as buildYoloopSuccessorTransitionPayloadHash,K as capRecoveryHistory,w as createDefaultCounters,we as createDefaultState,Z as fingerprintYoloopSession};
@@ -0,0 +1,326 @@
1
+ import { Buffer } from 'node:buffer';
2
+ export const FRONTMATTER_CONTEXTS = [
3
+ 'wrapper',
4
+ 'delegated-prompt',
5
+ 'epic-target',
6
+ 'story-target',
7
+ 'memory-wrapper',
8
+ ];
9
+ export const FRONTMATTER_STATUSES = [
10
+ 'absent',
11
+ 'valid',
12
+ 'malformed',
13
+ 'oversized',
14
+ ];
15
+ export const FRONTMATTER_REASON_CODES = [
16
+ 'frontmatter-absent',
17
+ 'frontmatter-valid',
18
+ 'frontmatter-malformed',
19
+ 'frontmatter-oversized',
20
+ 'frontmatter-preserved',
21
+ 'frontmatter-projection-withheld',
22
+ 'frontmatter-explicit-selection-required',
23
+ 'frontmatter-metadata-projected',
24
+ 'frontmatter-unknown-fields-omitted',
25
+ 'frontmatter-unsupported-value',
26
+ ];
27
+ export const FRONTMATTER_LIMITS = Object.freeze({
28
+ maxBytes: 8 * 1024,
29
+ maxLines: 128,
30
+ maxFields: 32,
31
+ maxListItems: 32,
32
+ maxValueChars: 512,
33
+ });
34
+ const EPIC_TARGET_FIELDS = Object.freeze([
35
+ 'id',
36
+ 'title',
37
+ 'status',
38
+ 'source',
39
+ 'depends_on',
40
+ 'related_epics',
41
+ 'total_stories',
42
+ 'completed_stories',
43
+ ]);
44
+ const STORY_TARGET_FIELDS = Object.freeze([
45
+ 'id',
46
+ 'epic_id',
47
+ 'title',
48
+ 'status',
49
+ 'priority',
50
+ 'depends_on',
51
+ 'files_to_modify',
52
+ ]);
53
+ const LIST_FIELDS = new Set([
54
+ 'depends_on',
55
+ 'related_epics',
56
+ 'files_to_modify',
57
+ ]);
58
+ const NUMBER_FIELDS = new Set([
59
+ 'total_stories',
60
+ 'completed_stories',
61
+ ]);
62
+ export const FRONTMATTER_CONTEXT_POLICY = Object.freeze({
63
+ wrapper: Object.freeze({
64
+ preserveSource: true,
65
+ metadataProjection: 'withheld',
66
+ bodyProjection: 'explicit-command-only',
67
+ allowedFields: Object.freeze([]),
68
+ }),
69
+ 'delegated-prompt': Object.freeze({
70
+ preserveSource: true,
71
+ metadataProjection: 'withheld',
72
+ bodyProjection: 'server-authorized-command-only',
73
+ allowedFields: Object.freeze([]),
74
+ }),
75
+ 'epic-target': Object.freeze({
76
+ preserveSource: true,
77
+ metadataProjection: 'explicit-target-only',
78
+ bodyProjection: 'none',
79
+ allowedFields: EPIC_TARGET_FIELDS,
80
+ }),
81
+ 'story-target': Object.freeze({
82
+ preserveSource: true,
83
+ metadataProjection: 'explicit-target-only',
84
+ bodyProjection: 'none',
85
+ allowedFields: STORY_TARGET_FIELDS,
86
+ }),
87
+ 'memory-wrapper': Object.freeze({
88
+ preserveSource: true,
89
+ metadataProjection: 'withheld',
90
+ bodyProjection: 'none',
91
+ allowedFields: Object.freeze([]),
92
+ }),
93
+ });
94
+ function byteLength(value) {
95
+ return Buffer.byteLength(value, 'utf8');
96
+ }
97
+ function scanFrontmatter(source) {
98
+ if (typeof source !== 'string')
99
+ return { status: 'malformed' };
100
+ if (!source.startsWith('---'))
101
+ return { status: 'absent' };
102
+ const opening = source.match(/^---(?:\r\n|\n)/);
103
+ if (!opening)
104
+ return { status: 'malformed' };
105
+ const contentStart = opening[0].length;
106
+ let cursor = contentStart;
107
+ let lineCount = 1;
108
+ while (cursor <= source.length) {
109
+ const newlineIndex = source.indexOf('\n', cursor);
110
+ const lineEnd = newlineIndex === -1 ? source.length : newlineIndex;
111
+ const rawLine = source.slice(cursor, lineEnd);
112
+ const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
113
+ lineCount += 1;
114
+ const inspectedEnd = newlineIndex === -1 ? lineEnd : newlineIndex + 1;
115
+ if (lineCount > FRONTMATTER_LIMITS.maxLines ||
116
+ byteLength(source.slice(0, inspectedEnd)) > FRONTMATTER_LIMITS.maxBytes) {
117
+ return { status: 'oversized' };
118
+ }
119
+ if (line === '---') {
120
+ return {
121
+ status: 'valid',
122
+ block: source.slice(contentStart, cursor),
123
+ };
124
+ }
125
+ if (newlineIndex === -1)
126
+ break;
127
+ cursor = newlineIndex + 1;
128
+ }
129
+ return { status: 'malformed' };
130
+ }
131
+ function unquote(value) {
132
+ if (value.length > FRONTMATTER_LIMITS.maxValueChars)
133
+ return null;
134
+ const first = value[0];
135
+ const last = value[value.length - 1];
136
+ const startsQuoted = first === '"' || first === "'";
137
+ const endsQuoted = last === '"' || last === "'";
138
+ if (startsQuoted !== endsQuoted || (startsQuoted && first !== last))
139
+ return null;
140
+ if (startsQuoted && value.length >= 2) {
141
+ return value.slice(1, -1);
142
+ }
143
+ return value;
144
+ }
145
+ function parseListItem(value) {
146
+ const parsed = unquote(value.trim());
147
+ if (parsed === null || parsed.length === 0)
148
+ return null;
149
+ return parsed;
150
+ }
151
+ function parseInlineValue(value) {
152
+ const trimmed = value.trim();
153
+ if (trimmed.length === 0)
154
+ return null;
155
+ if (trimmed.startsWith('[') || trimmed.endsWith(']')) {
156
+ if (!trimmed.startsWith('[') || !trimmed.endsWith(']'))
157
+ return null;
158
+ const body = trimmed.slice(1, -1).trim();
159
+ if (!body)
160
+ return Object.freeze([]);
161
+ const rawItems = body.split(',');
162
+ if (rawItems.length > FRONTMATTER_LIMITS.maxListItems)
163
+ return null;
164
+ const items = rawItems.map(parseListItem);
165
+ if (items.some((item) => item === null))
166
+ return null;
167
+ return Object.freeze(items);
168
+ }
169
+ const unquoted = unquote(trimmed);
170
+ if (unquoted === null)
171
+ return null;
172
+ const quoted = (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
173
+ (trimmed.startsWith("'") && trimmed.endsWith("'"));
174
+ if (quoted)
175
+ return unquoted;
176
+ if (unquoted === 'true')
177
+ return true;
178
+ if (unquoted === 'false')
179
+ return false;
180
+ if (/^-?(?:0|[1-9]\d*)$/.test(unquoted)) {
181
+ const numberValue = Number(unquoted);
182
+ if (!Number.isFinite(numberValue) || !Number.isSafeInteger(numberValue))
183
+ return null;
184
+ return numberValue;
185
+ }
186
+ return unquoted;
187
+ }
188
+ function parseFrontmatterBlock(block) {
189
+ const fields = {};
190
+ let fieldCount = 0;
191
+ let pendingListKey = null;
192
+ let pendingList = [];
193
+ const commitPendingList = () => {
194
+ if (!pendingListKey)
195
+ return true;
196
+ if (pendingList.length > FRONTMATTER_LIMITS.maxListItems)
197
+ return false;
198
+ fields[pendingListKey] = Object.freeze([...pendingList]);
199
+ pendingListKey = null;
200
+ pendingList = [];
201
+ return true;
202
+ };
203
+ for (const rawLine of block.split(/\r?\n/)) {
204
+ if (!rawLine.trim() || rawLine.trimStart().startsWith('#'))
205
+ continue;
206
+ const listMatch = rawLine.match(/^\s+-\s+(.+?)\s*$/);
207
+ if (listMatch) {
208
+ if (!pendingListKey)
209
+ return { status: 'malformed', unsupportedValue: true };
210
+ if (listMatch[1].trim().length > FRONTMATTER_LIMITS.maxValueChars) {
211
+ return { status: 'oversized' };
212
+ }
213
+ const item = parseListItem(listMatch[1]);
214
+ if (item === null)
215
+ return { status: 'malformed', unsupportedValue: true };
216
+ pendingList.push(item);
217
+ if (pendingList.length > FRONTMATTER_LIMITS.maxListItems)
218
+ return { status: 'oversized' };
219
+ continue;
220
+ }
221
+ if (!commitPendingList())
222
+ return { status: 'oversized' };
223
+ const fieldMatch = rawLine.match(/^([A-Za-z_][A-Za-z0-9_-]{0,63}):(?:\s*(.*))?$/);
224
+ if (!fieldMatch)
225
+ return { status: 'malformed', unsupportedValue: true };
226
+ const key = fieldMatch[1];
227
+ if (Object.prototype.hasOwnProperty.call(fields, key)) {
228
+ return { status: 'malformed', unsupportedValue: true };
229
+ }
230
+ fieldCount += 1;
231
+ if (fieldCount > FRONTMATTER_LIMITS.maxFields)
232
+ return { status: 'oversized' };
233
+ const rawValue = fieldMatch[2] ?? '';
234
+ if (!rawValue.trim()) {
235
+ pendingListKey = key;
236
+ continue;
237
+ }
238
+ const value = parseInlineValue(rawValue);
239
+ if (value === null) {
240
+ return rawValue.length > FRONTMATTER_LIMITS.maxValueChars
241
+ ? { status: 'oversized' }
242
+ : { status: 'malformed', unsupportedValue: true };
243
+ }
244
+ fields[key] = value;
245
+ }
246
+ if (!commitPendingList())
247
+ return { status: 'oversized' };
248
+ return { status: 'valid', fields: Object.freeze(fields) };
249
+ }
250
+ function result(context, status, reasonCodes, metadata = Object.freeze({})) {
251
+ return Object.freeze({
252
+ context,
253
+ status,
254
+ reasonCodes: Object.freeze([...new Set(reasonCodes)]),
255
+ metadata: Object.freeze({ ...metadata }),
256
+ });
257
+ }
258
+ function isValidAllowedFieldValue(key, value) {
259
+ if (LIST_FIELDS.has(key)) {
260
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
261
+ }
262
+ if (NUMBER_FIELDS.has(key)) {
263
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
264
+ }
265
+ return typeof value === 'string';
266
+ }
267
+ /**
268
+ * Classify bounded frontmatter without returning source text, body content, or
269
+ * rejected values. The caller supplies the context; file content never grants
270
+ * execution authority or selects its own interpretation mode.
271
+ */
272
+ export function inspectFrontmatterContext(source, options) {
273
+ const structural = scanFrontmatter(source);
274
+ if (structural.status === 'absent') {
275
+ return result(options.context, 'absent', ['frontmatter-absent']);
276
+ }
277
+ if (structural.status === 'malformed') {
278
+ return result(options.context, 'malformed', ['frontmatter-malformed']);
279
+ }
280
+ if (structural.status === 'oversized') {
281
+ return result(options.context, 'oversized', ['frontmatter-oversized']);
282
+ }
283
+ const parsed = parseFrontmatterBlock(structural.block);
284
+ if (parsed.status === 'malformed') {
285
+ return result(options.context, 'malformed', [
286
+ 'frontmatter-malformed',
287
+ ...(parsed.unsupportedValue ? ['frontmatter-unsupported-value'] : []),
288
+ ]);
289
+ }
290
+ if (parsed.status === 'oversized') {
291
+ return result(options.context, 'oversized', ['frontmatter-oversized']);
292
+ }
293
+ const policy = FRONTMATTER_CONTEXT_POLICY[options.context];
294
+ const baseReasons = [
295
+ 'frontmatter-valid',
296
+ 'frontmatter-preserved',
297
+ ];
298
+ if (policy.metadataProjection === 'withheld') {
299
+ baseReasons.push('frontmatter-projection-withheld');
300
+ return result(options.context, 'valid', baseReasons);
301
+ }
302
+ if (options.explicitlySelected !== true) {
303
+ baseReasons.push('frontmatter-explicit-selection-required', 'frontmatter-projection-withheld');
304
+ return result(options.context, 'valid', baseReasons);
305
+ }
306
+ const allowedFields = new Set(policy.allowedFields);
307
+ const metadata = {};
308
+ let omittedUnknownField = false;
309
+ for (const [key, value] of Object.entries(parsed.fields)) {
310
+ if (!allowedFields.has(key)) {
311
+ omittedUnknownField = true;
312
+ continue;
313
+ }
314
+ if (!isValidAllowedFieldValue(key, value)) {
315
+ return result(options.context, 'malformed', [
316
+ 'frontmatter-malformed',
317
+ 'frontmatter-unsupported-value',
318
+ ]);
319
+ }
320
+ metadata[key] = value;
321
+ }
322
+ baseReasons.push('frontmatter-metadata-projected');
323
+ if (omittedUnknownField)
324
+ baseReasons.push('frontmatter-unknown-fields-omitted');
325
+ return result(options.context, 'valid', baseReasons, metadata);
326
+ }
@@ -2,11 +2,11 @@
2
2
  export { PUBLIC_PROJECTION_BUNDLE, PUBLIC_PROJECTION_GENERATION_ID, PUBLIC_PROJECTION_BUNDLE_HASH } from './public-projections.generated.js';
3
3
  export const COMMAND_REGISTRY_SCHEMA_VERSION = 1;
4
4
  export const COMMAND_REGISTRY_VERSION = "1.0.0";
5
- export const COMMAND_REGISTRY_CHECKSUM = "b9a97bc12316a913b72a27e1d4c18e749737da40003bd73f60350b07408bd5c7";
5
+ export const COMMAND_REGISTRY_CHECKSUM = "2dd14afa94738949eb19750f69590b6ddb0592cfb302ae1fe26073a88d408184";
6
6
  export const COMMAND_REGISTRY_PROJECTION_MANIFEST = {
7
7
  "schemaVersion": 1,
8
8
  "registryVersion": "1.0.0",
9
- "registryChecksum": "b9a97bc12316a913b72a27e1d4c18e749737da40003bd73f60350b07408bd5c7",
9
+ "registryChecksum": "2dd14afa94738949eb19750f69590b6ddb0592cfb302ae1fe26073a88d408184",
10
10
  "projections": [
11
11
  {
12
12
  "kind": "admin",
@@ -2,13 +2,13 @@
2
2
  export const GENERATED_INVOCATION_SCHEMA_MANIFEST = {
3
3
  "schemaVersion": "p205-invocation-schema-v1",
4
4
  "registryVersion": "1.0.0",
5
- "registryChecksum": "b9a97bc12316a913b72a27e1d4c18e749737da40003bd73f60350b07408bd5c7",
6
- "schemaChecksum": "15e75fe8a4d8251414b661821c52eb0a9e92a3b7bca0f0d05fcb1827c4e24ce2",
5
+ "registryChecksum": "2dd14afa94738949eb19750f69590b6ddb0592cfb302ae1fe26073a88d408184",
6
+ "schemaChecksum": "65ce7e99e9cf5511a9ab02838b31acf6f86752267df453041b26e05ad9feffbc",
7
7
  "commandCount": 62,
8
- "optionCount": 47,
8
+ "optionCount": 48,
9
9
  "positionalCount": 43,
10
10
  "operationCount": 35,
11
- "projectionSha256": "321924a0406826b225854d699683d72e5f905d14894168882a2cb81c06e988fc",
11
+ "projectionSha256": "58d6051ce4002dc45fefa70f73da0da0e932d8e903dc56ec822e14d9343e86e5",
12
12
  "commands": [
13
13
  {
14
14
  "commandId": "arch-api-contracts",
@@ -1821,6 +1821,18 @@ export const GENERATED_INVOCATION_SCHEMA_MANIFEST = {
1821
1821
  ],
1822
1822
  "valuePattern": null
1823
1823
  },
1824
+ {
1825
+ "aliases": [],
1826
+ "cardinality": "one",
1827
+ "default": false,
1828
+ "id": "new-run",
1829
+ "kind": "boolean",
1830
+ "required": false,
1831
+ "syntax": [
1832
+ "bare"
1833
+ ],
1834
+ "valuePattern": null
1835
+ },
1824
1836
  {
1825
1837
  "aliases": [],
1826
1838
  "cardinality": "one",