{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../../src/core/governance/ledger.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEX,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,kBAAkB,EAClB,0BAA0B,EAE1B,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,MAAM,YAAY,CAAC;AAMpB,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AACD,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,gBAAgB,CA8BpG;AAsBD,wBAAgB,yBAAyB,CACxC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,wBAAwB,GAC/B,uBAAuB,CAoCzB;AACD,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,oBAAoB,EAChC,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,qBAAqB,GACjC,gBAAgB,CASlB;AAED,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,kBAAkB,GAC1B,gBAAgB,CAQlB;AAED,wBAAgB,0BAA0B,CACzC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,0BAA0B,GAChC,gBAAgB,CAgBlB;AACD,qBAAa,mBAAoB,YAAW,eAAe;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,YAAY,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,EAG5D;IACD,OAAO,CAAC,IAAI;YAIE,MAAM;YAaN,IAAI;YAeJ,KAAK;IAYb,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC,CASrF;IACK,IAAI,CAAC,SAAS,EAAE,MAAM;;;;;;;;OAE3B;IACK,MAAM,CAAC,CAAC,EACb,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CACT,MAAM,EAAE,gBAAgB,KACpB;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE;;;;;;;;;;OAiBvF;CACD","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport * as fsp from \"node:fs/promises\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport type {\n\tGovernanceBudgetUsage,\n\tGovernanceConsumeRequest,\n\tGovernanceConsumeResult,\n\tGovernanceCorrelation,\n\tGovernanceDecision,\n\tGovernanceEscalationRecord,\n\tGovernanceEvent,\n\tGovernanceLedger,\n\tGovernanceRetryClass,\n\tGovernanceStore,\n} from \"./types.js\";\nimport { GOVERNANCE_DECISION_HISTORY_LIMIT } from \"./types.js\";\nimport { validateGovernanceLedger } from \"./validation.js\";\n\nconst SAFE_MISSION_ID = /^[A-Za-z0-9._-]+$/u;\n\nexport function isSafeGovernanceMissionId(missionId: string): boolean {\n\treturn SAFE_MISSION_ID.test(missionId) && missionId !== \".\" && missionId !== \"..\" && missionId.length <= 256;\n}\n\nexport function defaultGovernanceRoot(): string {\n\treturn process.env.JENSEN_GOVERNANCE_DIR?.trim() || path.join(os.homedir(), \".jensen\", \"governance\");\n}\nexport function createGovernanceLedger(missionId: string, parentMissionId?: string): GovernanceLedger {\n\treturn {\n\t\tschemaVersion: 1,\n\t\tmissionId,\n\t\tparentMissionId,\n\t\trevision: 0,\n\t\tusage: {\n\t\t\tturns: 0,\n\t\t\tcontextTokens: 0,\n\t\t\tgeneratedTokens: 0,\n\t\t\ttoolCalls: 0,\n\t\t\tretries: 0,\n\t\t\twallClockMs: 0,\n\t\t\tinferenceRequests: 0,\n\t\t\tchildren: 0,\n\t\t\tlogicalAgents: 0,\n\t\t\ttotalRetries: 0,\n\t\t\treplans: 0,\n\t\t\tfanOut: 0,\n\t\t\tdepth: 0,\n\t\t\treadyChildren: 0,\n\t\t\tcloudSpendUsd: 0,\n\t\t\tmodelEscalations: 0,\n\t\t},\n\t\tretries: { tool: 0, output_contract: 0, execution: 0, planner: 0, replan: 0, remote_execution: 0, provider: 0 },\n\t\tcost: { status: \"NONE\", knownUsd: 0, unknownPaidEvents: 0, localInferenceRequests: 0 },\n\t\tescalationHistory: [],\n\t\tdecisionHistory: [],\n\t\tevents: [],\n\t};\n}\nconst USAGE_KEYS = new Set<keyof GovernanceBudgetUsage>([\n\t\"turns\",\n\t\"contextTokens\",\n\t\"generatedTokens\",\n\t\"toolCalls\",\n\t\"retries\",\n\t\"wallClockMs\",\n\t\"inferenceRequests\",\n\t\"children\",\n\t\"logicalAgents\",\n\t\"totalRetries\",\n\t\"replans\",\n\t\"fanOut\",\n\t\"depth\",\n\t\"readyChildren\",\n\t\"cloudSpendUsd\",\n\t\"modelEscalations\",\n]);\nfunction clone<T>(value: T): T {\n\treturn JSON.parse(JSON.stringify(value)) as T;\n}\nexport function consumeGovernanceInternal(\n\tledger: GovernanceLedger,\n\trequest: GovernanceConsumeRequest,\n): GovernanceConsumeResult {\n\tif (!request.eventId.trim() || !USAGE_KEYS.has(request.resource))\n\t\treturn { allowed: false, reason: \"INVALID_CONSUMPTION\", ledger };\n\tif (!Number.isFinite(request.amount) || request.amount < 0)\n\t\treturn { allowed: false, reason: \"INVALID_AMOUNT\", ledger };\n\tif (ledger.events.some((event) => event.eventId === request.eventId)) return { allowed: true, ledger };\n\tconst next = clone(ledger);\n\tconst event: GovernanceEvent = {\n\t\teventId: request.eventId,\n\t\tscope: request.scope,\n\t\tkind: \"consume\",\n\t\tresource: request.resource,\n\t\tamount: request.amount,\n\t\tprovider: request.provider,\n\t\tmodel: request.model,\n\t\tcostStatus: request.costStatus,\n\t\tcostUsd: request.costUsd,\n\t\tchildId: request.childId,\n\t\tcorrelation: request.correlation,\n\t\tatMs: request.atMs,\n\t};\n\tnext.usage[request.resource] += request.amount;\n\tif (request.costStatus === \"UNKNOWN\" && request.provider && request.costUsd === undefined) {\n\t\tnext.cost.unknownPaidEvents += 1;\n\t\tnext.cost.status = \"UNKNOWN\";\n\t} else if (request.costUsd !== undefined && request.costUsd >= 0) {\n\t\tnext.cost.knownUsd += request.costUsd;\n\t\tnext.usage.cloudSpendUsd += request.costUsd;\n\t\tnext.cost.status = \"KNOWN\";\n\t} else if (request.provider?.startsWith(\"llamacpp-\")) {\n\t\tnext.cost.localInferenceRequests += request.amount;\n\t\tnext.cost.status = next.cost.unknownPaidEvents > 0 ? \"UNKNOWN\" : \"NONE\";\n\t}\n\tnext.events.push(event);\n\tnext.revision += 1;\n\treturn { allowed: true, ledger: next };\n}\nexport function recordGovernanceRetry(\n\tledger: GovernanceLedger,\n\teventId: string,\n\tretryClass: GovernanceRetryClass,\n\tatMs: number,\n\tcorrelation?: GovernanceCorrelation,\n): GovernanceLedger {\n\tif (ledger.events.some((event) => event.eventId === eventId)) return ledger;\n\tconst next = clone(ledger);\n\tnext.retries[retryClass] += 1;\n\tnext.usage.retries += 1;\n\tnext.usage.totalRetries += 1;\n\tnext.events.push({ eventId, scope: \"orchestration\", kind: \"retry\", retryClass, correlation, atMs });\n\tnext.revision += 1;\n\treturn next;\n}\n\nexport function recordGovernanceDecision(\n\tledger: GovernanceLedger,\n\teventId: string,\n\tdecision: GovernanceDecision,\n): GovernanceLedger {\n\tif (ledger.events.some((event) => event.eventId === eventId)) return ledger;\n\tconst next = clone(ledger);\n\tnext.lastDecision = decision;\n\tnext.decisionHistory = [...next.decisionHistory, decision].slice(-GOVERNANCE_DECISION_HISTORY_LIMIT);\n\tnext.events.push({ eventId, scope: \"session\", kind: \"decision\", reason: decision.reason, atMs: decision.atMs });\n\tnext.revision += 1;\n\treturn next;\n}\n\nexport function recordGovernanceEscalation(\n\tledger: GovernanceLedger,\n\teventId: string,\n\trecord: GovernanceEscalationRecord,\n): GovernanceLedger {\n\tif (ledger.events.some((event) => event.eventId === eventId)) return ledger;\n\tconst next = clone(ledger);\n\tnext.escalationHistory.push(record);\n\tnext.usage.modelEscalations += 1;\n\tnext.events.push({\n\t\teventId,\n\t\tscope: \"orchestration\",\n\t\tkind: \"escalation\",\n\t\tprovider: record.to.provider,\n\t\tmodel: record.to.model,\n\t\treason: record.reason,\n\t\tatMs: record.atMs,\n\t});\n\tnext.revision += 1;\n\treturn next;\n}\nexport class FileGovernanceStore implements GovernanceStore {\n\treadonly storeId: string;\n\tprivate readonly root: string;\n\tconstructor(options: { root?: string; storeId?: string } = {}) {\n\t\tthis.root = path.resolve(options.root ?? defaultGovernanceRoot());\n\t\tthis.storeId = options.storeId ?? \"file\";\n\t}\n\tprivate file(missionId: string): string {\n\t\tif (!isSafeGovernanceMissionId(missionId)) throw new Error(`UNSAFE_GOVERNANCE_MISSION_ID:${missionId}`);\n\t\treturn path.join(this.root, `${missionId}.json`);\n\t}\n\tprivate async locked<T>(missionId: string, fn: () => Promise<T>): Promise<T> {\n\t\tawait fsp.mkdir(this.root, { recursive: true });\n\t\tconst release = await lockfile.lock(this.file(missionId), {\n\t\t\trealpath: false,\n\t\t\tstale: 30_000,\n\t\t\tretries: { retries: 8, minTimeout: 20, maxTimeout: 250, factor: 2, randomize: true },\n\t\t});\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait release().catch(() => undefined);\n\t\t}\n\t}\n\tprivate async read(\n\t\tmissionId: string,\n\t): Promise<\n\t\t{ status: \"ok\"; ledger: GovernanceLedger } | { status: \"missing\" } | { status: \"corrupt\"; diagnostic: string }\n\t> {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(await fsp.readFile(this.file(missionId), \"utf8\")) as GovernanceLedger;\n\t\t\tconst validation = validateGovernanceLedger(parsed, missionId);\n\t\t\tif (!validation.valid) return { status: \"corrupt\", diagnostic: validation.diagnostic };\n\t\t\treturn { status: \"ok\", ledger: validation.ledger };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { status: \"missing\" };\n\t\t\treturn { status: \"corrupt\", diagnostic: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\tprivate async write(ledger: GovernanceLedger): Promise<void> {\n\t\tconst target = this.file(ledger.missionId);\n\t\tconst tmp = `${target}.${randomUUID()}.tmp`;\n\t\tconst handle = await fsp.open(tmp, \"w\");\n\t\ttry {\n\t\t\tawait handle.writeFile(JSON.stringify(ledger, null, 2), \"utf8\");\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fsp.rename(tmp, target);\n\t}\n\tasync create(ledger: GovernanceLedger): Promise<\"created\" | \"idempotent\" | \"conflict\"> {\n\t\treturn this.locked(ledger.missionId, async () => {\n\t\t\tconst existing = await this.read(ledger.missionId);\n\t\t\tif (existing.status === \"ok\")\n\t\t\t\treturn JSON.stringify(existing.ledger) === JSON.stringify(ledger) ? \"idempotent\" : \"conflict\";\n\t\t\tif (existing.status === \"corrupt\") return \"conflict\";\n\t\t\tawait this.write(ledger);\n\t\t\treturn \"created\";\n\t\t});\n\t}\n\tasync load(missionId: string) {\n\t\treturn this.read(missionId);\n\t}\n\tasync mutate<T>(\n\t\tmissionId: string,\n\t\tmutation: (\n\t\t\tledger: GovernanceLedger,\n\t\t) => { kind: \"write\"; ledger: GovernanceLedger; value: T } | { kind: \"noop\"; value: T },\n\t) {\n\t\treturn this.locked(missionId, async () => {\n\t\t\tconst current = await this.read(missionId);\n\t\t\tif (current.status !== \"ok\")\n\t\t\t\treturn current.status === \"missing\"\n\t\t\t\t\t? current\n\t\t\t\t\t: { status: \"corrupt\" as const, diagnostic: current.diagnostic };\n\t\t\tconst output = mutation(current.ledger);\n\t\t\tif (output.kind === \"write\") {\n\t\t\t\tconst validation = validateGovernanceLedger(output.ledger, missionId);\n\t\t\t\tif (!validation.valid) throw new Error(`INVALID_GOVERNANCE_MUTATION:${validation.diagnostic}`);\n\t\t\t\tif (output.ledger.revision < current.ledger.revision) throw new Error(\"GOVERNANCE_REVISION_REGRESSION\");\n\t\t\t\tawait this.write(output.ledger);\n\t\t\t}\n\t\t\treturn { status: \"ok\" as const, value: output.value };\n\t\t});\n\t}\n}\n"]}