instar 0.28.93 → 0.28.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/DriftSpendLedger.d.ts +112 -0
- package/dist/core/DriftSpendLedger.d.ts.map +1 -0
- package/dist/core/DriftSpendLedger.js +285 -0
- package/dist/core/DriftSpendLedger.js.map +1 -0
- package/dist/core/ProjectDriftChecker.d.ts +15 -0
- package/dist/core/ProjectDriftChecker.d.ts.map +1 -1
- package/dist/core/ProjectDriftChecker.js +87 -2
- package/dist/core/ProjectDriftChecker.js.map +1 -1
- package/dist/core/ProjectDriftCheckerCache.d.ts +118 -0
- package/dist/core/ProjectDriftCheckerCache.d.ts.map +1 -0
- package/dist/core/ProjectDriftCheckerCache.js +221 -0
- package/dist/core/ProjectDriftCheckerCache.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/0.28.94.md +60 -0
- package/upgrades/side-effects/project-scope-phase1b-pr2.md +198 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DriftSpendLedger — daily-rotated, lock-coordinated cost ledger for the
|
|
3
|
+
* drift checker.
|
|
4
|
+
*
|
|
5
|
+
* Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.4 ("Cost ceiling").
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - Total drift-check spend per agent ≤ DEFAULT_DAILY_CAP_USD ($1) per UTC
|
|
9
|
+
* day. Cap enforcement is `spent + estimated > cap → reject` (strict
|
|
10
|
+
* `>` boundary, per spec corrected from iter 2).
|
|
11
|
+
* - One file per UTC day at `.instar/drift-spend-YYYY-MM-DD.jsonl`.
|
|
12
|
+
* - Old files retained ≤ DEFAULT_RETENTION_DAYS (30). Anything older is
|
|
13
|
+
* archived to `.instar/drift-spend-archive/<year>-<month>.tar.gz` by
|
|
14
|
+
* `pruneOlderThan()` (caller-driven; no internal timer).
|
|
15
|
+
* - Each call pre-reserves estimated cost in an append-only row
|
|
16
|
+
* `{recordId, projectId, estimatedCost, actualCost?: null, timestamp}`.
|
|
17
|
+
* After the call returns, `reconcile(recordId, actualCost)` appends a
|
|
18
|
+
* second row with `actualCost` set, so a tally over the day is
|
|
19
|
+
* `sum(estimatedCost where actualCost is null) +
|
|
20
|
+
* sum(actualCost where present)` — O(rows in the day).
|
|
21
|
+
* - Advisory file lock on `.instar/local/drift-spend.lock` via
|
|
22
|
+
* proper-lockfile (consistent with AgentRegistry, SharedStateLedger,
|
|
23
|
+
* PlatformActivityRegistry). Lock file lives under `.instar/local/`
|
|
24
|
+
* (machine-local, NOT git-synced — same convention as
|
|
25
|
+
* `round-runner.lock`).
|
|
26
|
+
* - Multi-machine semantics: each machine writes its own ledger. The
|
|
27
|
+
* cap is per-machine, summed via git-sync at the file layer. The spec
|
|
28
|
+
* documents this as "up to N machines × cap/day in worst case"; the
|
|
29
|
+
* true atomic cross-machine cap is a separately-tracked deferred
|
|
30
|
+
* child (`drift-spend-cross-machine`).
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_DAILY_CAP_USD = 1;
|
|
33
|
+
export declare const DEFAULT_RETENTION_DAYS = 30;
|
|
34
|
+
export interface DriftSpendLedgerConfig {
|
|
35
|
+
/** Absolute path to the agent's `.instar/` directory. */
|
|
36
|
+
stateDir: string;
|
|
37
|
+
/** Cap in USD per UTC day. Defaults to $1. Tests can lower. */
|
|
38
|
+
dailyCapUsd?: number;
|
|
39
|
+
/** Retention window in days; defaults to 30. */
|
|
40
|
+
retentionDays?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ReserveInput {
|
|
43
|
+
projectId: string;
|
|
44
|
+
/** Best-effort upfront estimate; refined by `reconcile`. */
|
|
45
|
+
estimatedCost: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ReserveResult {
|
|
48
|
+
/** Stable id used to reconcile actualCost later. */
|
|
49
|
+
recordId: string;
|
|
50
|
+
/** Cumulative spent today AFTER this reservation. */
|
|
51
|
+
spentToday: number;
|
|
52
|
+
}
|
|
53
|
+
export declare class OverBudgetError extends Error {
|
|
54
|
+
capUsd: number;
|
|
55
|
+
spentToday: number;
|
|
56
|
+
estimatedCost: number;
|
|
57
|
+
constructor(capUsd: number, spentToday: number, estimatedCost: number);
|
|
58
|
+
}
|
|
59
|
+
export declare class DriftSpendLedger {
|
|
60
|
+
private stateDir;
|
|
61
|
+
private capUsd;
|
|
62
|
+
private retentionDays;
|
|
63
|
+
constructor(config: DriftSpendLedgerConfig);
|
|
64
|
+
/**
|
|
65
|
+
* Pre-reserve `estimatedCost`. Throws `OverBudgetError` if doing so would
|
|
66
|
+
* exceed the daily cap. The reservation is appended as a row with
|
|
67
|
+
* `actualCost: null`; the caller MUST call `reconcile()` after the LLM
|
|
68
|
+
* returns. If the caller dies before reconcile, the reservation stays in
|
|
69
|
+
* place as `estimatedCost` and contributes to the cap until UTC rollover.
|
|
70
|
+
*/
|
|
71
|
+
reserve(input: ReserveInput): Promise<ReserveResult>;
|
|
72
|
+
/**
|
|
73
|
+
* Record the actual cost after the LLM call. Appends a SECOND row with
|
|
74
|
+
* the same `recordId`, `estimatedCost: 0`, and `actualCost: <real>`.
|
|
75
|
+
* `tallySpent()` already de-duplicates by recordId (a present `actualCost`
|
|
76
|
+
* supersedes any pending `estimatedCost` rows for the same id).
|
|
77
|
+
*
|
|
78
|
+
* Idempotent: calling reconcile twice with the same recordId+actualCost
|
|
79
|
+
* is harmless — the tally still resolves to one actual row.
|
|
80
|
+
*/
|
|
81
|
+
reconcile(recordId: string, actualCost: number): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Cumulative spent today, lock-coordinated for safety.
|
|
84
|
+
* Useful for digests / dashboards.
|
|
85
|
+
*/
|
|
86
|
+
spentToday(): Promise<number>;
|
|
87
|
+
/**
|
|
88
|
+
* Prune ledger files older than `retentionDays`. NOT called automatically;
|
|
89
|
+
* the caller (server startup, a scheduled job, or a CLI) decides when.
|
|
90
|
+
* Files older than the window are moved to
|
|
91
|
+
* `.instar/drift-spend-archive/<year>-<month>.tar.gz` — but for Phase 1
|
|
92
|
+
* we just delete them via SafeFsExecutor. The tarball archive is a
|
|
93
|
+
* later refinement (the spec describes it but explicitly leaves the
|
|
94
|
+
* concrete archive format open).
|
|
95
|
+
*/
|
|
96
|
+
pruneOlderThan(now?: Date): Promise<{
|
|
97
|
+
removed: string[];
|
|
98
|
+
}>;
|
|
99
|
+
private ensureDirs;
|
|
100
|
+
private lockTargetPath;
|
|
101
|
+
private todayLedgerPath;
|
|
102
|
+
private yesterdayLedgerPath;
|
|
103
|
+
private ledgerPathForDate;
|
|
104
|
+
private readRows;
|
|
105
|
+
private appendRow;
|
|
106
|
+
/**
|
|
107
|
+
* Sum cost across a row set: present `actualCost` supersedes the
|
|
108
|
+
* matching pending `estimatedCost`. O(rows).
|
|
109
|
+
*/
|
|
110
|
+
private tallySpent;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=DriftSpendLedger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DriftSpendLedger.d.ts","sourceRoot":"","sources":["../../src/core/DriftSpendLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AASH,eAAO,MAAM,qBAAqB,IAAM,CAAC;AACzC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAQzC,MAAM,WAAW,sBAAsB;IACrC,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACrB,MAAM,EAAE,MAAM;IAAS,UAAU,EAAE,MAAM;IAAS,aAAa,EAAE,MAAM;gBAAvE,MAAM,EAAE,MAAM,EAAS,UAAU,EAAE,MAAM,EAAS,aAAa,EAAE,MAAM;CAM3F;AAUD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAS;gBAElB,MAAM,EAAE,sBAAsB;IAM1C;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IA6B1D;;;;;;;;OAQG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCpE;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAUnC;;;;;;;;OAQG;IACG,cAAc,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA0B5E,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,QAAQ;IAwBhB,OAAO,CAAC,SAAS;IAIjB;;;OAGG;IACH,OAAO,CAAC,UAAU;CAkBnB"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DriftSpendLedger — daily-rotated, lock-coordinated cost ledger for the
|
|
3
|
+
* drift checker.
|
|
4
|
+
*
|
|
5
|
+
* Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.4 ("Cost ceiling").
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - Total drift-check spend per agent ≤ DEFAULT_DAILY_CAP_USD ($1) per UTC
|
|
9
|
+
* day. Cap enforcement is `spent + estimated > cap → reject` (strict
|
|
10
|
+
* `>` boundary, per spec corrected from iter 2).
|
|
11
|
+
* - One file per UTC day at `.instar/drift-spend-YYYY-MM-DD.jsonl`.
|
|
12
|
+
* - Old files retained ≤ DEFAULT_RETENTION_DAYS (30). Anything older is
|
|
13
|
+
* archived to `.instar/drift-spend-archive/<year>-<month>.tar.gz` by
|
|
14
|
+
* `pruneOlderThan()` (caller-driven; no internal timer).
|
|
15
|
+
* - Each call pre-reserves estimated cost in an append-only row
|
|
16
|
+
* `{recordId, projectId, estimatedCost, actualCost?: null, timestamp}`.
|
|
17
|
+
* After the call returns, `reconcile(recordId, actualCost)` appends a
|
|
18
|
+
* second row with `actualCost` set, so a tally over the day is
|
|
19
|
+
* `sum(estimatedCost where actualCost is null) +
|
|
20
|
+
* sum(actualCost where present)` — O(rows in the day).
|
|
21
|
+
* - Advisory file lock on `.instar/local/drift-spend.lock` via
|
|
22
|
+
* proper-lockfile (consistent with AgentRegistry, SharedStateLedger,
|
|
23
|
+
* PlatformActivityRegistry). Lock file lives under `.instar/local/`
|
|
24
|
+
* (machine-local, NOT git-synced — same convention as
|
|
25
|
+
* `round-runner.lock`).
|
|
26
|
+
* - Multi-machine semantics: each machine writes its own ledger. The
|
|
27
|
+
* cap is per-machine, summed via git-sync at the file layer. The spec
|
|
28
|
+
* documents this as "up to N machines × cap/day in worst case"; the
|
|
29
|
+
* true atomic cross-machine cap is a separately-tracked deferred
|
|
30
|
+
* child (`drift-spend-cross-machine`).
|
|
31
|
+
*/
|
|
32
|
+
import fs from 'node:fs';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
import crypto from 'node:crypto';
|
|
35
|
+
import lockfile from 'proper-lockfile';
|
|
36
|
+
import { SafeFsExecutor } from './SafeFsExecutor.js';
|
|
37
|
+
export const DEFAULT_DAILY_CAP_USD = 1.0;
|
|
38
|
+
export const DEFAULT_RETENTION_DAYS = 30;
|
|
39
|
+
/** proper-lockfile options that match the rest of the codebase. */
|
|
40
|
+
const LOCK_OPTIONS = {
|
|
41
|
+
retries: { retries: 50, factor: 1.2, minTimeout: 50, maxTimeout: 500 },
|
|
42
|
+
stale: 10_000,
|
|
43
|
+
realpath: false,
|
|
44
|
+
};
|
|
45
|
+
export class OverBudgetError extends Error {
|
|
46
|
+
capUsd;
|
|
47
|
+
spentToday;
|
|
48
|
+
estimatedCost;
|
|
49
|
+
constructor(capUsd, spentToday, estimatedCost) {
|
|
50
|
+
super(`drift-spend daily cap exceeded: spent ${spentToday.toFixed(4)} + estimated ${estimatedCost.toFixed(4)} > cap ${capUsd.toFixed(2)}`);
|
|
51
|
+
this.capUsd = capUsd;
|
|
52
|
+
this.spentToday = spentToday;
|
|
53
|
+
this.estimatedCost = estimatedCost;
|
|
54
|
+
this.name = 'OverBudgetError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export class DriftSpendLedger {
|
|
58
|
+
stateDir;
|
|
59
|
+
capUsd;
|
|
60
|
+
retentionDays;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.stateDir = config.stateDir;
|
|
63
|
+
this.capUsd = config.dailyCapUsd ?? DEFAULT_DAILY_CAP_USD;
|
|
64
|
+
this.retentionDays = config.retentionDays ?? DEFAULT_RETENTION_DAYS;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Pre-reserve `estimatedCost`. Throws `OverBudgetError` if doing so would
|
|
68
|
+
* exceed the daily cap. The reservation is appended as a row with
|
|
69
|
+
* `actualCost: null`; the caller MUST call `reconcile()` after the LLM
|
|
70
|
+
* returns. If the caller dies before reconcile, the reservation stays in
|
|
71
|
+
* place as `estimatedCost` and contributes to the cap until UTC rollover.
|
|
72
|
+
*/
|
|
73
|
+
async reserve(input) {
|
|
74
|
+
if (!Number.isFinite(input.estimatedCost) || input.estimatedCost < 0) {
|
|
75
|
+
throw new Error(`reserve: estimatedCost must be a non-negative finite number; got ${input.estimatedCost}`);
|
|
76
|
+
}
|
|
77
|
+
this.ensureDirs();
|
|
78
|
+
const release = await lockfile.lock(this.lockTargetPath(), LOCK_OPTIONS);
|
|
79
|
+
try {
|
|
80
|
+
const today = this.todayLedgerPath();
|
|
81
|
+
const rows = this.readRows(today);
|
|
82
|
+
const spentToday = this.tallySpent(rows);
|
|
83
|
+
// Strict greater-than per spec § Phase 1.4. Equal-to-cap is allowed.
|
|
84
|
+
if (spentToday + input.estimatedCost > this.capUsd) {
|
|
85
|
+
throw new OverBudgetError(this.capUsd, spentToday, input.estimatedCost);
|
|
86
|
+
}
|
|
87
|
+
const recordId = crypto.randomBytes(8).toString('hex');
|
|
88
|
+
const row = {
|
|
89
|
+
recordId,
|
|
90
|
+
projectId: input.projectId,
|
|
91
|
+
estimatedCost: input.estimatedCost,
|
|
92
|
+
actualCost: null,
|
|
93
|
+
timestamp: new Date().toISOString(),
|
|
94
|
+
};
|
|
95
|
+
this.appendRow(today, row);
|
|
96
|
+
return { recordId, spentToday: spentToday + input.estimatedCost };
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await release();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Record the actual cost after the LLM call. Appends a SECOND row with
|
|
104
|
+
* the same `recordId`, `estimatedCost: 0`, and `actualCost: <real>`.
|
|
105
|
+
* `tallySpent()` already de-duplicates by recordId (a present `actualCost`
|
|
106
|
+
* supersedes any pending `estimatedCost` rows for the same id).
|
|
107
|
+
*
|
|
108
|
+
* Idempotent: calling reconcile twice with the same recordId+actualCost
|
|
109
|
+
* is harmless — the tally still resolves to one actual row.
|
|
110
|
+
*/
|
|
111
|
+
async reconcile(recordId, actualCost) {
|
|
112
|
+
if (!Number.isFinite(actualCost) || actualCost < 0) {
|
|
113
|
+
throw new Error(`reconcile: actualCost must be a non-negative finite number; got ${actualCost}`);
|
|
114
|
+
}
|
|
115
|
+
this.ensureDirs();
|
|
116
|
+
const release = await lockfile.lock(this.lockTargetPath(), LOCK_OPTIONS);
|
|
117
|
+
try {
|
|
118
|
+
// Walk forward through TODAY first (the common case), then yesterday
|
|
119
|
+
// (in case the LLM call straddled UTC midnight). Beyond that we
|
|
120
|
+
// refuse — a record that lived more than ~24h is stale anyway.
|
|
121
|
+
const candidates = [this.todayLedgerPath(), this.yesterdayLedgerPath()];
|
|
122
|
+
for (const file of candidates) {
|
|
123
|
+
const rows = this.readRows(file);
|
|
124
|
+
if (!rows.some((r) => r.recordId === recordId))
|
|
125
|
+
continue;
|
|
126
|
+
const row = {
|
|
127
|
+
recordId,
|
|
128
|
+
projectId: rows.find((r) => r.recordId === recordId)?.projectId ?? '<reconcile>',
|
|
129
|
+
estimatedCost: 0,
|
|
130
|
+
actualCost,
|
|
131
|
+
timestamp: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
this.appendRow(file, row);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// No matching reservation found. Still record the actualCost — the
|
|
137
|
+
// caller's spend is real even if the bookkeeping is messy. Today's
|
|
138
|
+
// file is the right home.
|
|
139
|
+
const row = {
|
|
140
|
+
recordId,
|
|
141
|
+
projectId: '<unknown>',
|
|
142
|
+
estimatedCost: 0,
|
|
143
|
+
actualCost,
|
|
144
|
+
timestamp: new Date().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
this.appendRow(this.todayLedgerPath(), row);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
await release();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Cumulative spent today, lock-coordinated for safety.
|
|
154
|
+
* Useful for digests / dashboards.
|
|
155
|
+
*/
|
|
156
|
+
async spentToday() {
|
|
157
|
+
this.ensureDirs();
|
|
158
|
+
const release = await lockfile.lock(this.lockTargetPath(), LOCK_OPTIONS);
|
|
159
|
+
try {
|
|
160
|
+
return this.tallySpent(this.readRows(this.todayLedgerPath()));
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
await release();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Prune ledger files older than `retentionDays`. NOT called automatically;
|
|
168
|
+
* the caller (server startup, a scheduled job, or a CLI) decides when.
|
|
169
|
+
* Files older than the window are moved to
|
|
170
|
+
* `.instar/drift-spend-archive/<year>-<month>.tar.gz` — but for Phase 1
|
|
171
|
+
* we just delete them via SafeFsExecutor. The tarball archive is a
|
|
172
|
+
* later refinement (the spec describes it but explicitly leaves the
|
|
173
|
+
* concrete archive format open).
|
|
174
|
+
*/
|
|
175
|
+
async pruneOlderThan(now = new Date()) {
|
|
176
|
+
this.ensureDirs();
|
|
177
|
+
const release = await lockfile.lock(this.lockTargetPath(), LOCK_OPTIONS);
|
|
178
|
+
try {
|
|
179
|
+
const dir = this.stateDir;
|
|
180
|
+
const cutoff = new Date(now);
|
|
181
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - this.retentionDays);
|
|
182
|
+
const removed = [];
|
|
183
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
184
|
+
const m = entry.match(/^drift-spend-(\d{4}-\d{2}-\d{2})\.jsonl$/);
|
|
185
|
+
if (!m)
|
|
186
|
+
continue;
|
|
187
|
+
const fileDate = new Date(m[1] + 'T00:00:00Z');
|
|
188
|
+
if (fileDate < cutoff) {
|
|
189
|
+
const abs = path.join(dir, entry);
|
|
190
|
+
SafeFsExecutor.safeRmSync(abs, { force: true, operation: 'src/core/DriftSpendLedger.ts:pruneOlderThan' });
|
|
191
|
+
removed.push(entry);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { removed };
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
await release();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// ── Internals ─────────────────────────────────────────────────────
|
|
201
|
+
ensureDirs() {
|
|
202
|
+
if (!fs.existsSync(this.stateDir))
|
|
203
|
+
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
204
|
+
const localDir = path.join(this.stateDir, 'local');
|
|
205
|
+
if (!fs.existsSync(localDir))
|
|
206
|
+
fs.mkdirSync(localDir, { recursive: true });
|
|
207
|
+
// proper-lockfile needs the target file to exist.
|
|
208
|
+
const lockTarget = this.lockTargetPath();
|
|
209
|
+
if (!fs.existsSync(lockTarget)) {
|
|
210
|
+
fs.writeFileSync(lockTarget, '');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
lockTargetPath() {
|
|
214
|
+
return path.join(this.stateDir, 'local', 'drift-spend.lock');
|
|
215
|
+
}
|
|
216
|
+
todayLedgerPath() {
|
|
217
|
+
return this.ledgerPathForDate(new Date());
|
|
218
|
+
}
|
|
219
|
+
yesterdayLedgerPath() {
|
|
220
|
+
const d = new Date();
|
|
221
|
+
d.setUTCDate(d.getUTCDate() - 1);
|
|
222
|
+
return this.ledgerPathForDate(d);
|
|
223
|
+
}
|
|
224
|
+
ledgerPathForDate(d) {
|
|
225
|
+
const utc = d.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
226
|
+
return path.join(this.stateDir, `drift-spend-${utc}.jsonl`);
|
|
227
|
+
}
|
|
228
|
+
readRows(file) {
|
|
229
|
+
if (!fs.existsSync(file))
|
|
230
|
+
return [];
|
|
231
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
232
|
+
const lines = raw.split('\n').filter((l) => l.trim().length > 0);
|
|
233
|
+
const rows = [];
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
try {
|
|
236
|
+
const obj = JSON.parse(line);
|
|
237
|
+
if (typeof obj.recordId !== 'string')
|
|
238
|
+
continue;
|
|
239
|
+
if (typeof obj.projectId !== 'string')
|
|
240
|
+
continue;
|
|
241
|
+
if (typeof obj.estimatedCost !== 'number')
|
|
242
|
+
continue;
|
|
243
|
+
if (obj.actualCost !== null && typeof obj.actualCost !== 'number')
|
|
244
|
+
continue;
|
|
245
|
+
if (typeof obj.timestamp !== 'string')
|
|
246
|
+
continue;
|
|
247
|
+
rows.push(obj);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Skip malformed rows. The append path always writes valid JSON,
|
|
251
|
+
// so any corruption is either external editing or partial-write
|
|
252
|
+
// — neither should crash the checker.
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return rows;
|
|
257
|
+
}
|
|
258
|
+
appendRow(file, row) {
|
|
259
|
+
fs.appendFileSync(file, JSON.stringify(row) + '\n', { mode: 0o600 });
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Sum cost across a row set: present `actualCost` supersedes the
|
|
263
|
+
* matching pending `estimatedCost`. O(rows).
|
|
264
|
+
*/
|
|
265
|
+
tallySpent(rows) {
|
|
266
|
+
// De-dup by recordId: actualCost present wins; otherwise sum estimatedCost.
|
|
267
|
+
const byId = new Map();
|
|
268
|
+
for (const r of rows) {
|
|
269
|
+
const existing = byId.get(r.recordId) ?? { estimated: 0, actual: null };
|
|
270
|
+
if (r.actualCost !== null) {
|
|
271
|
+
existing.actual = r.actualCost;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
existing.estimated = r.estimatedCost;
|
|
275
|
+
}
|
|
276
|
+
byId.set(r.recordId, existing);
|
|
277
|
+
}
|
|
278
|
+
let total = 0;
|
|
279
|
+
for (const v of byId.values()) {
|
|
280
|
+
total += v.actual ?? v.estimated;
|
|
281
|
+
}
|
|
282
|
+
return total;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=DriftSpendLedger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DriftSpendLedger.js","sourceRoot":"","sources":["../../src/core/DriftSpendLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,QAAQ,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AACzC,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,mEAAmE;AACnE,MAAM,YAAY,GAAyB;IACzC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;IACtE,KAAK,EAAE,MAAM;IACb,QAAQ,EAAE,KAAK;CAChB,CAAC;AAwBF,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACrB;IAAuB;IAA2B;IAArE,YAAmB,MAAc,EAAS,UAAkB,EAAS,aAAqB;QACxF,KAAK,CACH,yCAAyC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CACpI,CAAC;QAHe,WAAM,GAAN,MAAM,CAAQ;QAAS,eAAU,GAAV,UAAU,CAAQ;QAAS,kBAAa,GAAb,aAAa,CAAQ;QAIxF,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAUD,MAAM,OAAO,gBAAgB;IACnB,QAAQ,CAAS;IACjB,MAAM,CAAS;IACf,aAAa,CAAS;IAE9B,YAAY,MAA8B;QACxC,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,IAAI,qBAAqB,CAAC;QAC1D,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACtE,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,KAAmB;QAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,oEAAoE,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC;QAC7G,CAAC;QACD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,YAAY,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACzC,qEAAqE;YACrE,IAAI,UAAU,GAAG,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACnD,MAAM,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACvD,MAAM,GAAG,GAAc;gBACrB,QAAQ;gBACR,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;gBAClC,UAAU,EAAE,IAAI;gBAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAC3B,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC;QACpE,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,SAAS,CAAC,QAAgB,EAAE,UAAkB;QAClD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,mEAAmE,UAAU,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,YAAY,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,qEAAqE;YACrE,gEAAgE;YAChE,+DAA+D;YAC/D,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC;YACxE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACjC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC;oBAAE,SAAS;gBACzD,MAAM,GAAG,GAAc;oBACrB,QAAQ;oBACR,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,SAAS,IAAI,aAAa;oBAChF,aAAa,EAAE,CAAC;oBAChB,UAAU;oBACV,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC,CAAC;gBACF,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC1B,OAAO;YACT,CAAC;YACD,mEAAmE;YACnE,mEAAmE;YACnE,0BAA0B;YAC1B,MAAM,GAAG,GAAc;gBACrB,QAAQ;gBACR,SAAS,EAAE,WAAW;gBACtB,aAAa,EAAE,CAAC;gBAChB,UAAU;gBACV,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,CAAC,CAAC;QAC9C,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,YAAY,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,cAAc,CAAC,MAAY,IAAI,IAAI,EAAE;QACzC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,YAAY,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC1B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,MAAM,OAAO,GAAa,EAAE,CAAC;YAC7B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxC,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;gBAClE,IAAI,CAAC,CAAC;oBAAE,SAAS;gBACjB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC;gBAC/C,IAAI,QAAQ,GAAG,MAAM,EAAE,CAAC;oBACtB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;oBAClC,cAAc,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,6CAA6C,EAAE,CAAC,CAAC;oBAC1G,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED,qEAAqE;IAE7D,UAAU;QAChB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1E,kDAAkD;QAClD,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAEO,cAAc;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC;IAC/D,CAAC;IAEO,eAAe;QACrB,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAEO,mBAAmB;QACzB,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IAEO,iBAAiB,CAAC,CAAO;QAC/B,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa;QACvD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,QAAQ,CAAC,CAAC;IAC9D,CAAC;IAEO,QAAQ,CAAC,IAAY;QAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjE,MAAM,IAAI,GAAgB,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC7B,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;oBAAE,SAAS;gBAC/C,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;oBAAE,SAAS;gBAChD,IAAI,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ;oBAAE,SAAS;gBACpD,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ;oBAAE,SAAS;gBAC5E,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;oBAAE,SAAS;gBAChD,IAAI,CAAC,IAAI,CAAC,GAAgB,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC;gBACP,iEAAiE;gBACjE,gEAAgE;gBAChE,sCAAsC;gBACtC,SAAS;YACX,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,SAAS,CAAC,IAAY,EAAE,GAAc;QAC5C,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvE,CAAC;IAED;;;OAGG;IACK,UAAU,CAAC,IAAiB;QAClC,4EAA4E;QAC5E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAwD,CAAC;QAC7E,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACxE,IAAI,CAAC,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;gBAC1B,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC,aAAa,CAAC;YACvC,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9B,KAAK,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
* wires this up.
|
|
59
59
|
*/
|
|
60
60
|
import type { DriftVerdict, IntelligenceProvider, VerifiedCitation } from './types.js';
|
|
61
|
+
import type { ProjectDriftCheckerCache } from './ProjectDriftCheckerCache.js';
|
|
62
|
+
import { DriftSpendLedger } from './DriftSpendLedger.js';
|
|
61
63
|
/** Bumped whenever the system prompt changes; part of the future cache key. */
|
|
62
64
|
export declare const DRIFT_PROMPT_TEMPLATE_VERSION = 1;
|
|
63
65
|
/** Hard limits per spec § Phase 1.4. Exported so tests can dial them down. */
|
|
@@ -127,10 +129,23 @@ export interface ProjectDriftCheckerConfig {
|
|
|
127
129
|
intelligence?: IntelligenceProvider;
|
|
128
130
|
/** Defaults to spec § Phase 1.4 hard limits; tests can override. */
|
|
129
131
|
limits?: Partial<typeof DRIFT_LIMITS>;
|
|
132
|
+
/** Optional verdict cache. When absent, every call recomputes from scratch. */
|
|
133
|
+
cache?: ProjectDriftCheckerCache;
|
|
134
|
+
/** Optional spend ledger. When absent, no cap enforcement. */
|
|
135
|
+
ledger?: DriftSpendLedger;
|
|
136
|
+
/**
|
|
137
|
+
* Best-effort cost estimate per call in USD; ledger pre-reserves this
|
|
138
|
+
* amount before the LLM call. Default $0.01 — conservative for the
|
|
139
|
+
* 'balanced' tier on the 50k-token-budget cap.
|
|
140
|
+
*/
|
|
141
|
+
estimatedCostPerCallUsd?: number;
|
|
130
142
|
}
|
|
131
143
|
export declare class ProjectDriftChecker {
|
|
132
144
|
private intelligence?;
|
|
133
145
|
private limits;
|
|
146
|
+
private cache?;
|
|
147
|
+
private ledger?;
|
|
148
|
+
private estimatedCostPerCallUsd;
|
|
134
149
|
constructor(config?: ProjectDriftCheckerConfig);
|
|
135
150
|
run(input: DriftCheckInput): Promise<DriftVerdict>;
|
|
136
151
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDriftChecker.d.ts","sourceRoot":"","sources":["../../src/core/ProjectDriftChecker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ProjectDriftChecker.d.ts","sourceRoot":"","sources":["../../src/core/ProjectDriftChecker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAmB,MAAM,uBAAuB,CAAC;AAE1E,+EAA+E;AAC/E,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,8EAA8E;AAC9E,eAAO,MAAM,YAAY;;;;;IAKvB,kFAAkF;;;;CAI1E,CAAC;AAIX,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,YAAY;IACpB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,UAAU,kBAAkB;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,oEAAoE;IACpE,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,YAAY,CAAC,CAAC;IACtC,+EAA+E;IAC/E,KAAK,CAAC,EAAE,wBAAwB,CAAC;IACjC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,YAAY,CAAC,CAAuB;IAC5C,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAAC,CAA2B;IACzC,OAAO,CAAC,MAAM,CAAC,CAAmB;IAClC,OAAO,CAAC,uBAAuB,CAAS;gBAE5B,MAAM,GAAE,yBAA8B;IAQ5C,GAAG,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC;CA2RzD;AAYD,cAAM,YAAa,SAAQ,KAAK;gBAClB,EAAE,EAAE,MAAM;CAIvB;AAkBD,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAgDtD;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CA0CjE;AAED,KAAK,iBAAiB,GAClB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,UAAU,GAAG,aAAa,GAAG,kBAAkB,CAAC;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,kBAAkB,EAAE,CAAC;CACjC,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,CA2B5E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,KAAK,EAAE,YAAY,EAAE,EACrB,UAAU,EAAE,MAAM,GACjB,gBAAgB,EAAE,CAuBpB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,qBAAqB,EAAE,MAAM,EAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7D;IAAE,qBAAqB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAE,CAMrG;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
import crypto from 'node:crypto';
|
|
61
61
|
import fs from 'node:fs';
|
|
62
62
|
import { jailPath } from './StageTransitionValidator.js';
|
|
63
|
+
import { OverBudgetError } from './DriftSpendLedger.js';
|
|
63
64
|
/** Bumped whenever the system prompt changes; part of the future cache key. */
|
|
64
65
|
export const DRIFT_PROMPT_TEMPLATE_VERSION = 1;
|
|
65
66
|
/** Hard limits per spec § Phase 1.4. Exported so tests can dial them down. */
|
|
@@ -77,9 +78,15 @@ const VALID_VERDICTS = new Set(['no-drift', 'minor-drift', 'premise-violated']);
|
|
|
77
78
|
export class ProjectDriftChecker {
|
|
78
79
|
intelligence;
|
|
79
80
|
limits;
|
|
81
|
+
cache;
|
|
82
|
+
ledger;
|
|
83
|
+
estimatedCostPerCallUsd;
|
|
80
84
|
constructor(config = {}) {
|
|
81
85
|
this.intelligence = config.intelligence;
|
|
82
86
|
this.limits = { ...DRIFT_LIMITS, ...(config.limits ?? {}) };
|
|
87
|
+
this.cache = config.cache;
|
|
88
|
+
this.ledger = config.ledger;
|
|
89
|
+
this.estimatedCostPerCallUsd = config.estimatedCostPerCallUsd ?? 0.01;
|
|
83
90
|
}
|
|
84
91
|
async run(input) {
|
|
85
92
|
// No provider → can't compute a signal; surface to user attention.
|
|
@@ -187,11 +194,58 @@ export class ProjectDriftChecker {
|
|
|
187
194
|
rationale: `estimated ${estimatedTokens} tokens > budget ${this.limits.totalTokenBudget}`,
|
|
188
195
|
};
|
|
189
196
|
}
|
|
197
|
+
// ── Cache lookup ──────────────────────────────────────────────
|
|
198
|
+
// After validation but BEFORE the LLM call. A hit returns the
|
|
199
|
+
// cached verdict directly; no ledger spend, no provider call.
|
|
200
|
+
const resolvedModelId = input.modelId ?? 'balanced';
|
|
201
|
+
let cacheKey;
|
|
202
|
+
if (this.cache) {
|
|
203
|
+
const lookup = this.cache.lookup({
|
|
204
|
+
projectId: input.projectId,
|
|
205
|
+
roundIndex: input.roundIndex,
|
|
206
|
+
promptTemplateVersion: DRIFT_PROMPT_TEMPLATE_VERSION,
|
|
207
|
+
modelId: resolvedModelId,
|
|
208
|
+
specPath: specJailed.absPath,
|
|
209
|
+
referencedFilePaths: prepared.map((p) => p.absPath),
|
|
210
|
+
referencedFileBytes: prepared.map((p) => p.bytes),
|
|
211
|
+
specBytes,
|
|
212
|
+
});
|
|
213
|
+
if (lookup.hit) {
|
|
214
|
+
return lookup.verdict;
|
|
215
|
+
}
|
|
216
|
+
cacheKey = lookup.key;
|
|
217
|
+
}
|
|
218
|
+
// ── Ledger pre-reserve ────────────────────────────────────────
|
|
219
|
+
// Strict spec § Phase 1.4: `spent + estimated > cap` → reject as
|
|
220
|
+
// manual-review-required (over-budget). The reserve appends a pending
|
|
221
|
+
// row; reconcile() at the end of the call upgrades it with the actual
|
|
222
|
+
// cost. If the call throws/crashes, the pending estimate still counts
|
|
223
|
+
// toward the cap until UTC rollover — fail-safe rather than fail-open.
|
|
224
|
+
let ledgerRecordId;
|
|
225
|
+
if (this.ledger) {
|
|
226
|
+
try {
|
|
227
|
+
const reservation = await this.ledger.reserve({
|
|
228
|
+
projectId: input.projectId,
|
|
229
|
+
estimatedCost: this.estimatedCostPerCallUsd,
|
|
230
|
+
});
|
|
231
|
+
ledgerRecordId = reservation.recordId;
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
if (err instanceof OverBudgetError) {
|
|
235
|
+
return {
|
|
236
|
+
verdict: 'manual-review-required',
|
|
237
|
+
reason: 'over-budget',
|
|
238
|
+
rationale: err.message,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
190
244
|
// ── Build prompt ──────────────────────────────────────────────
|
|
191
245
|
const prompt = buildPrompt({
|
|
192
246
|
specBody: specBytes.toString('utf-8'),
|
|
193
247
|
files: prepared,
|
|
194
|
-
modelId:
|
|
248
|
+
modelId: resolvedModelId,
|
|
195
249
|
templateVersion: DRIFT_PROMPT_TEMPLATE_VERSION,
|
|
196
250
|
deletedFiles: deleted,
|
|
197
251
|
});
|
|
@@ -257,11 +311,42 @@ export class ProjectDriftChecker {
|
|
|
257
311
|
rationale: 'LLM produced citations but none resolved against actual file contents',
|
|
258
312
|
};
|
|
259
313
|
}
|
|
260
|
-
|
|
314
|
+
const finalVerdict = {
|
|
261
315
|
verdict,
|
|
262
316
|
rationale,
|
|
263
317
|
evidenceCitations: verified,
|
|
264
318
|
};
|
|
319
|
+
// ── Cache + ledger reconciliation ─────────────────────────────
|
|
320
|
+
// Cache only "real" verdicts — no-drift / minor-drift / premise-violated.
|
|
321
|
+
// LLM-failure paths (schema-fail / timeout) are not cached so a retry
|
|
322
|
+
// can succeed. Input-validation failures (over-budget etc.) are cheap
|
|
323
|
+
// to recompute and don't benefit from caching.
|
|
324
|
+
if (this.cache && cacheKey) {
|
|
325
|
+
this.cache.put({
|
|
326
|
+
projectId: input.projectId,
|
|
327
|
+
roundIndex: input.roundIndex,
|
|
328
|
+
promptTemplateVersion: DRIFT_PROMPT_TEMPLATE_VERSION,
|
|
329
|
+
modelId: resolvedModelId,
|
|
330
|
+
specPath: specJailed.absPath,
|
|
331
|
+
referencedFilePaths: prepared.map((p) => p.absPath),
|
|
332
|
+
referencedFileBytes: prepared.map((p) => p.bytes),
|
|
333
|
+
specBytes,
|
|
334
|
+
}, cacheKey, finalVerdict);
|
|
335
|
+
}
|
|
336
|
+
if (this.ledger && ledgerRecordId) {
|
|
337
|
+
// Best-effort reconcile. We don't currently get usage stats back from
|
|
338
|
+
// the provider abstraction, so actual ≈ estimated. When a future
|
|
339
|
+
// provider returns token usage, reconcile to the real number.
|
|
340
|
+
try {
|
|
341
|
+
await this.ledger.reconcile(ledgerRecordId, this.estimatedCostPerCallUsd);
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// Reconcile failure is non-fatal — the pending reservation already
|
|
345
|
+
// counts toward the cap, so the worst case is a slight overshoot
|
|
346
|
+
// of "spent" vs "actually spent" until UTC rollover.
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return finalVerdict;
|
|
265
350
|
}
|
|
266
351
|
}
|
|
267
352
|
// ── Helpers ───────────────────────────────────────────────────────────
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDriftChecker.js","sourceRoot":"","sources":["../../src/core/ProjectDriftChecker.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectDriftChecker.js","sourceRoot":"","sources":["../../src/core/ProjectDriftChecker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectDriftCheckerCache — verdict cache with mtime fast-path.
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.4 ("Cache key" + mtime
|
|
5
|
+
* fast-path).
|
|
6
|
+
*
|
|
7
|
+
* Cache key: sha256(promptTemplateVersion + modelId + specBodySha + sortedFileHashes)
|
|
8
|
+
* TTL: 24h (cache entries older than this are treated as misses)
|
|
9
|
+
*
|
|
10
|
+
* Mtime fast-path:
|
|
11
|
+
* Before computing the cache key, the cache compares (specPath mtime,
|
|
12
|
+
* referencedFile mtimes) to the values it last saw associated with the
|
|
13
|
+
* stored entry. If every mtime matches, the cache key is REUSED without
|
|
14
|
+
* re-hashing — file contents haven't changed. If any mtime moved, full
|
|
15
|
+
* hashing falls back. This is the spec's stated optimization: hashing
|
|
16
|
+
* only happens when an mtime moved.
|
|
17
|
+
*
|
|
18
|
+
* Storage:
|
|
19
|
+
* In-memory Map keyed by `${projectId}:${roundIndex}` (the natural id
|
|
20
|
+
* for a drift check). Disk-backed snapshot at
|
|
21
|
+
* `.instar/drift-verdict-cache.json` so the cache survives restarts
|
|
22
|
+
* without a cold first-call penalty after a sleep/wake.
|
|
23
|
+
*
|
|
24
|
+
* Concurrency:
|
|
25
|
+
* This cache is consulted INSIDE the per-project mutex held by
|
|
26
|
+
* `POST /projects/:id/drift-check` (Phase 1.5 round runner has its own
|
|
27
|
+
* round-runner lock). Within a single process there's no race; across
|
|
28
|
+
* processes the JSON snapshot is read on construction and written on
|
|
29
|
+
* every put, atomically (tmp + rename).
|
|
30
|
+
*/
|
|
31
|
+
import type { DriftVerdict } from './types.js';
|
|
32
|
+
export declare const DEFAULT_TTL_MS: number;
|
|
33
|
+
export declare const SNAPSHOT_FILENAME = "drift-verdict-cache.json";
|
|
34
|
+
export interface ProjectDriftCheckerCacheConfig {
|
|
35
|
+
/** Absolute path to the agent's `.instar/` directory. */
|
|
36
|
+
stateDir: string;
|
|
37
|
+
/** Override the 24h TTL (tests dial this down). */
|
|
38
|
+
ttlMs?: number;
|
|
39
|
+
/** When false, the cache does not read/write disk (in-memory only). */
|
|
40
|
+
persist?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface CacheLookupInput {
|
|
43
|
+
projectId: string;
|
|
44
|
+
roundIndex: number;
|
|
45
|
+
/** Bumped on every prompt-template edit; part of the cache key. */
|
|
46
|
+
promptTemplateVersion: number;
|
|
47
|
+
/** Resolved model id from the provider; part of the cache key. */
|
|
48
|
+
modelId: string;
|
|
49
|
+
/** Absolute path to the spec file (used for mtime fast-path). */
|
|
50
|
+
specPath: string;
|
|
51
|
+
/** Absolute paths to referenced files. Order matters for fast-path but
|
|
52
|
+
* not for the cache key (sortedFileHashes is order-stable). */
|
|
53
|
+
referencedFilePaths: string[];
|
|
54
|
+
/** Buffers, in the same order as `referencedFilePaths`. Used to compute
|
|
55
|
+
* sha256 inputs when mtime fast-path misses. */
|
|
56
|
+
referencedFileBytes: Buffer[];
|
|
57
|
+
/** Spec body bytes — sha256 input. */
|
|
58
|
+
specBytes: Buffer;
|
|
59
|
+
}
|
|
60
|
+
export interface CacheHit {
|
|
61
|
+
hit: true;
|
|
62
|
+
verdict: DriftVerdict;
|
|
63
|
+
computedAt: string;
|
|
64
|
+
/** Cache key that matched. */
|
|
65
|
+
key: string;
|
|
66
|
+
/** True if served by the mtime fast-path (no re-hash performed). */
|
|
67
|
+
mtimeFastPath: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface CacheMiss {
|
|
70
|
+
hit: false;
|
|
71
|
+
/** Cache key the caller should associate with the verdict on put(). */
|
|
72
|
+
key: string;
|
|
73
|
+
}
|
|
74
|
+
export type CacheLookupResult = CacheHit | CacheMiss;
|
|
75
|
+
export declare class ProjectDriftCheckerCache {
|
|
76
|
+
private stateDir;
|
|
77
|
+
private ttlMs;
|
|
78
|
+
private persist;
|
|
79
|
+
private entries;
|
|
80
|
+
constructor(config: ProjectDriftCheckerCacheConfig);
|
|
81
|
+
/**
|
|
82
|
+
* Look up an entry for `(projectId, roundIndex)`. Returns a hit ONLY if:
|
|
83
|
+
* (a) an entry exists, AND
|
|
84
|
+
* (b) the entry is within the TTL, AND
|
|
85
|
+
* (c) either the mtime fast-path matches (no re-hash needed) OR the
|
|
86
|
+
* full sha256 cache key matches.
|
|
87
|
+
*
|
|
88
|
+
* On miss, the returned `key` is the freshly-computed sha256, which the
|
|
89
|
+
* caller should pass back to `put()` after running drift.
|
|
90
|
+
*/
|
|
91
|
+
lookup(input: CacheLookupInput): CacheLookupResult;
|
|
92
|
+
/**
|
|
93
|
+
* Store `verdict` under `(projectId, roundIndex)` keyed by `key`. Captures
|
|
94
|
+
* file mtimes for the next mtime fast-path lookup.
|
|
95
|
+
*
|
|
96
|
+
* `manual-review-required` verdicts ARE cached too — re-running drift
|
|
97
|
+
* immediately won't change a deterministic over-budget or
|
|
98
|
+
* empty-spec answer. The TTL still expires them after 24h.
|
|
99
|
+
*/
|
|
100
|
+
put(input: CacheLookupInput, key: string, verdict: DriftVerdict): void;
|
|
101
|
+
/** Remove an entry for `(projectId, roundIndex)`. */
|
|
102
|
+
invalidate(projectId: string, roundIndex: number): void;
|
|
103
|
+
/** For tests / digest. */
|
|
104
|
+
size(): number;
|
|
105
|
+
private snapshotPath;
|
|
106
|
+
private loadSnapshot;
|
|
107
|
+
private saveSnapshot;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compute the deterministic cache key. Exported for tests and for the
|
|
111
|
+
* `cacheKeyInputs` helper in ProjectDriftChecker.ts (which produces the
|
|
112
|
+
* same inputs — this function hashes them into a stable string).
|
|
113
|
+
*/
|
|
114
|
+
export declare function computeCacheKey(promptTemplateVersion: number, modelId: string, specBytes: Buffer, referencedFileBytes: Array<{
|
|
115
|
+
relPath: string;
|
|
116
|
+
bytes: Buffer;
|
|
117
|
+
}>): string;
|
|
118
|
+
//# sourceMappingURL=ProjectDriftCheckerCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectDriftCheckerCache.d.ts","sourceRoot":"","sources":["../../src/core/ProjectDriftCheckerCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,eAAO,MAAM,cAAc,QAAsB,CAAC;AAClD,eAAO,MAAM,iBAAiB,6BAA6B,CAAC;AAE5D,MAAM,WAAW,8BAA8B;IAC7C,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB;oEACgE;IAChE,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B;qDACiD;IACjD,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAC;CACxB;AACD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,KAAK,CAAC;IACX,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAerD,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,OAAO,CAAiC;gBAEpC,MAAM,EAAE,8BAA8B;IAOlD;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB;IAqElD;;;;;;;OAOG;IACH,GAAG,CACD,KAAK,EAAE,gBAAgB,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACpB,IAAI;IA6BP,qDAAqD;IACrD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAKvD,0BAA0B;IAC1B,IAAI,IAAI,MAAM;IAMd,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,YAAY;CAOrB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,qBAAqB,EAAE,MAAM,EAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7D,MAAM,CAUR"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectDriftCheckerCache — verdict cache with mtime fast-path.
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.4 ("Cache key" + mtime
|
|
5
|
+
* fast-path).
|
|
6
|
+
*
|
|
7
|
+
* Cache key: sha256(promptTemplateVersion + modelId + specBodySha + sortedFileHashes)
|
|
8
|
+
* TTL: 24h (cache entries older than this are treated as misses)
|
|
9
|
+
*
|
|
10
|
+
* Mtime fast-path:
|
|
11
|
+
* Before computing the cache key, the cache compares (specPath mtime,
|
|
12
|
+
* referencedFile mtimes) to the values it last saw associated with the
|
|
13
|
+
* stored entry. If every mtime matches, the cache key is REUSED without
|
|
14
|
+
* re-hashing — file contents haven't changed. If any mtime moved, full
|
|
15
|
+
* hashing falls back. This is the spec's stated optimization: hashing
|
|
16
|
+
* only happens when an mtime moved.
|
|
17
|
+
*
|
|
18
|
+
* Storage:
|
|
19
|
+
* In-memory Map keyed by `${projectId}:${roundIndex}` (the natural id
|
|
20
|
+
* for a drift check). Disk-backed snapshot at
|
|
21
|
+
* `.instar/drift-verdict-cache.json` so the cache survives restarts
|
|
22
|
+
* without a cold first-call penalty after a sleep/wake.
|
|
23
|
+
*
|
|
24
|
+
* Concurrency:
|
|
25
|
+
* This cache is consulted INSIDE the per-project mutex held by
|
|
26
|
+
* `POST /projects/:id/drift-check` (Phase 1.5 round runner has its own
|
|
27
|
+
* round-runner lock). Within a single process there's no race; across
|
|
28
|
+
* processes the JSON snapshot is read on construction and written on
|
|
29
|
+
* every put, atomically (tmp + rename).
|
|
30
|
+
*/
|
|
31
|
+
import fs from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
import crypto from 'node:crypto';
|
|
34
|
+
export const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
35
|
+
export const SNAPSHOT_FILENAME = 'drift-verdict-cache.json';
|
|
36
|
+
export class ProjectDriftCheckerCache {
|
|
37
|
+
stateDir;
|
|
38
|
+
ttlMs;
|
|
39
|
+
persist;
|
|
40
|
+
entries = new Map();
|
|
41
|
+
constructor(config) {
|
|
42
|
+
this.stateDir = config.stateDir;
|
|
43
|
+
this.ttlMs = config.ttlMs ?? DEFAULT_TTL_MS;
|
|
44
|
+
this.persist = config.persist ?? true;
|
|
45
|
+
if (this.persist)
|
|
46
|
+
this.loadSnapshot();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Look up an entry for `(projectId, roundIndex)`. Returns a hit ONLY if:
|
|
50
|
+
* (a) an entry exists, AND
|
|
51
|
+
* (b) the entry is within the TTL, AND
|
|
52
|
+
* (c) either the mtime fast-path matches (no re-hash needed) OR the
|
|
53
|
+
* full sha256 cache key matches.
|
|
54
|
+
*
|
|
55
|
+
* On miss, the returned `key` is the freshly-computed sha256, which the
|
|
56
|
+
* caller should pass back to `put()` after running drift.
|
|
57
|
+
*/
|
|
58
|
+
lookup(input) {
|
|
59
|
+
const entryId = `${input.projectId}:${input.roundIndex}`;
|
|
60
|
+
const existing = this.entries.get(entryId);
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
// ── Mtime fast-path ──────────────────────────────────────────
|
|
63
|
+
// Cheap: verify the non-content cache-key inputs (templateVersion,
|
|
64
|
+
// modelId) match, then stat() each file and compare mtimes to the
|
|
65
|
+
// recorded mtimes. If everything matches, the cached entry is still
|
|
66
|
+
// valid and the cache key the entry holds is the one that matches
|
|
67
|
+
// the current bytes — we don't need to re-hash.
|
|
68
|
+
if (existing &&
|
|
69
|
+
now - Date.parse(existing.computedAt) < this.ttlMs &&
|
|
70
|
+
existing.promptTemplateVersion === input.promptTemplateVersion &&
|
|
71
|
+
existing.modelId === input.modelId) {
|
|
72
|
+
try {
|
|
73
|
+
const specMtime = fs.statSync(input.specPath).mtimeMs;
|
|
74
|
+
if (specMtime === existing.specMtimeMs) {
|
|
75
|
+
const allMatch = input.referencedFilePaths.every((p) => {
|
|
76
|
+
const recorded = existing.referencedFileMtimesMs[p];
|
|
77
|
+
if (recorded === undefined)
|
|
78
|
+
return false;
|
|
79
|
+
try {
|
|
80
|
+
return fs.statSync(p).mtimeMs === recorded;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// Also require referenced set hasn't shrunk OR grown
|
|
87
|
+
const sameSet = Object.keys(existing.referencedFileMtimesMs).length === input.referencedFilePaths.length;
|
|
88
|
+
if (allMatch && sameSet) {
|
|
89
|
+
return {
|
|
90
|
+
hit: true,
|
|
91
|
+
verdict: existing.verdict,
|
|
92
|
+
computedAt: existing.computedAt,
|
|
93
|
+
key: existing.key,
|
|
94
|
+
mtimeFastPath: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// statSync failure on the spec or a referenced file = treat as miss.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ── Full hash path ───────────────────────────────────────────
|
|
104
|
+
const key = computeCacheKey(input.promptTemplateVersion, input.modelId, input.specBytes, input.referencedFilePaths.map((p, i) => ({
|
|
105
|
+
relPath: p,
|
|
106
|
+
bytes: input.referencedFileBytes[i] ?? Buffer.alloc(0),
|
|
107
|
+
})));
|
|
108
|
+
if (existing && existing.key === key && now - Date.parse(existing.computedAt) < this.ttlMs) {
|
|
109
|
+
return {
|
|
110
|
+
hit: true,
|
|
111
|
+
verdict: existing.verdict,
|
|
112
|
+
computedAt: existing.computedAt,
|
|
113
|
+
key,
|
|
114
|
+
mtimeFastPath: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return { hit: false, key };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Store `verdict` under `(projectId, roundIndex)` keyed by `key`. Captures
|
|
121
|
+
* file mtimes for the next mtime fast-path lookup.
|
|
122
|
+
*
|
|
123
|
+
* `manual-review-required` verdicts ARE cached too — re-running drift
|
|
124
|
+
* immediately won't change a deterministic over-budget or
|
|
125
|
+
* empty-spec answer. The TTL still expires them after 24h.
|
|
126
|
+
*/
|
|
127
|
+
put(input, key, verdict) {
|
|
128
|
+
const entryId = `${input.projectId}:${input.roundIndex}`;
|
|
129
|
+
const referencedFileMtimesMs = {};
|
|
130
|
+
let specMtimeMs = 0;
|
|
131
|
+
try {
|
|
132
|
+
specMtimeMs = fs.statSync(input.specPath).mtimeMs;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Spec gone — caller will probably get an `empty-spec` verdict
|
|
136
|
+
// anyway; record a zero so the next fast-path correctly invalidates.
|
|
137
|
+
}
|
|
138
|
+
for (const p of input.referencedFilePaths) {
|
|
139
|
+
try {
|
|
140
|
+
referencedFileMtimesMs[p] = fs.statSync(p).mtimeMs;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
referencedFileMtimesMs[p] = -1; // Forces a future miss.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.entries.set(entryId, {
|
|
147
|
+
key,
|
|
148
|
+
verdict,
|
|
149
|
+
computedAt: new Date().toISOString(),
|
|
150
|
+
promptTemplateVersion: input.promptTemplateVersion,
|
|
151
|
+
modelId: input.modelId,
|
|
152
|
+
specMtimeMs,
|
|
153
|
+
referencedFileMtimesMs,
|
|
154
|
+
});
|
|
155
|
+
if (this.persist)
|
|
156
|
+
this.saveSnapshot();
|
|
157
|
+
}
|
|
158
|
+
/** Remove an entry for `(projectId, roundIndex)`. */
|
|
159
|
+
invalidate(projectId, roundIndex) {
|
|
160
|
+
this.entries.delete(`${projectId}:${roundIndex}`);
|
|
161
|
+
if (this.persist)
|
|
162
|
+
this.saveSnapshot();
|
|
163
|
+
}
|
|
164
|
+
/** For tests / digest. */
|
|
165
|
+
size() {
|
|
166
|
+
return this.entries.size;
|
|
167
|
+
}
|
|
168
|
+
// ── Snapshot persistence (atomic write) ───────────────────────────
|
|
169
|
+
snapshotPath() {
|
|
170
|
+
return path.join(this.stateDir, SNAPSHOT_FILENAME);
|
|
171
|
+
}
|
|
172
|
+
loadSnapshot() {
|
|
173
|
+
const p = this.snapshotPath();
|
|
174
|
+
if (!fs.existsSync(p))
|
|
175
|
+
return;
|
|
176
|
+
try {
|
|
177
|
+
const raw = fs.readFileSync(p, 'utf-8');
|
|
178
|
+
const obj = JSON.parse(raw);
|
|
179
|
+
if (!obj || !Array.isArray(obj.entries))
|
|
180
|
+
return;
|
|
181
|
+
for (const [id, entry] of obj.entries) {
|
|
182
|
+
if (typeof id !== 'string')
|
|
183
|
+
continue;
|
|
184
|
+
if (!entry || typeof entry !== 'object')
|
|
185
|
+
continue;
|
|
186
|
+
if (typeof entry.key !== 'string')
|
|
187
|
+
continue;
|
|
188
|
+
this.entries.set(id, entry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Corrupt snapshot → start fresh, will be overwritten on next put.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
saveSnapshot() {
|
|
196
|
+
if (!fs.existsSync(this.stateDir))
|
|
197
|
+
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
198
|
+
const tmp = this.snapshotPath() + '.tmp';
|
|
199
|
+
const body = JSON.stringify({ entries: Array.from(this.entries.entries()) });
|
|
200
|
+
fs.writeFileSync(tmp, body, { mode: 0o600 });
|
|
201
|
+
fs.renameSync(tmp, this.snapshotPath());
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Compute the deterministic cache key. Exported for tests and for the
|
|
206
|
+
* `cacheKeyInputs` helper in ProjectDriftChecker.ts (which produces the
|
|
207
|
+
* same inputs — this function hashes them into a stable string).
|
|
208
|
+
*/
|
|
209
|
+
export function computeCacheKey(promptTemplateVersion, modelId, specBytes, referencedFileBytes) {
|
|
210
|
+
const sortedFileEntries = referencedFileBytes
|
|
211
|
+
.map((f) => `${f.relPath}:${crypto.createHash('sha256').update(f.bytes).digest('hex')}`)
|
|
212
|
+
.sort();
|
|
213
|
+
const h = crypto.createHash('sha256');
|
|
214
|
+
h.update(`v=${promptTemplateVersion}\n`);
|
|
215
|
+
h.update(`m=${modelId}\n`);
|
|
216
|
+
h.update(`s=${crypto.createHash('sha256').update(specBytes).digest('hex')}\n`);
|
|
217
|
+
for (const entry of sortedFileEntries)
|
|
218
|
+
h.update(`f=${entry}\n`);
|
|
219
|
+
return h.digest('hex');
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=ProjectDriftCheckerCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectDriftCheckerCache.js","sourceRoot":"","sources":["../../src/core/ProjectDriftCheckerCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AAIjC,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AA4D5D,MAAM,OAAO,wBAAwB;IAC3B,QAAQ,CAAS;IACjB,KAAK,CAAS;IACd,OAAO,CAAU;IACjB,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEhD,YAAY,MAAsC;QAChD,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,cAAc,CAAC;QAC5C,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC;QACtC,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,YAAY,EAAE,CAAC;IACxC,CAAC;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAuB;QAC5B,MAAM,OAAO,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,gEAAgE;QAChE,mEAAmE;QACnE,kEAAkE;QAClE,oEAAoE;QACpE,kEAAkE;QAClE,gDAAgD;QAChD,IACE,QAAQ;YACR,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK;YAClD,QAAQ,CAAC,qBAAqB,KAAK,KAAK,CAAC,qBAAqB;YAC9D,QAAQ,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,EAClC,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;gBACtD,IAAI,SAAS,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC;oBACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;wBACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;wBACpD,IAAI,QAAQ,KAAK,SAAS;4BAAE,OAAO,KAAK,CAAC;wBACzC,IAAI,CAAC;4BACH,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC;wBAC7C,CAAC;wBAAC,MAAM,CAAC;4BACP,OAAO,KAAK,CAAC;wBACf,CAAC;oBACH,CAAC,CAAC,CAAC;oBACH,qDAAqD;oBACrD,MAAM,OAAO,GACX,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC;oBAC3F,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC;wBACxB,OAAO;4BACL,GAAG,EAAE,IAAI;4BACT,OAAO,EAAE,QAAQ,CAAC,OAAO;4BACzB,UAAU,EAAE,QAAQ,CAAC,UAAU;4BAC/B,GAAG,EAAE,QAAQ,CAAC,GAAG;4BACjB,aAAa,EAAE,IAAI;yBACpB,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,qEAAqE;YACvE,CAAC;QACH,CAAC;QAED,gEAAgE;QAChE,MAAM,GAAG,GAAG,eAAe,CACzB,KAAK,CAAC,qBAAqB,EAC3B,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACvC,OAAO,EAAE,CAAC;YACV,KAAK,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;SACvD,CAAC,CAAC,CACJ,CAAC;QACF,IAAI,QAAQ,IAAI,QAAQ,CAAC,GAAG,KAAK,GAAG,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3F,OAAO;gBACL,GAAG,EAAE,IAAI;gBACT,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,GAAG;gBACH,aAAa,EAAE,KAAK;aACrB,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,GAAG,CACD,KAAuB,EACvB,GAAW,EACX,OAAqB;QAErB,MAAM,OAAO,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACzD,MAAM,sBAAsB,GAA2B,EAAE,CAAC;QAC1D,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC;YACH,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;YAC/D,qEAAqE;QACvE,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,sBAAsB,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,wBAAwB;YAC1D,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE;YACxB,GAAG;YACH,OAAO;YACP,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;YAClD,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,WAAW;YACX,sBAAsB;SACvB,CAAC,CAAC;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,YAAY,EAAE,CAAC;IACxC,CAAC;IAED,qDAAqD;IACrD,UAAU,CAAC,SAAiB,EAAE,UAAkB;QAC9C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC,CAAC;QAClD,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,YAAY,EAAE,CAAC;IACxC,CAAC;IAED,0BAA0B;IAC1B,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,qEAAqE;IAE7D,YAAY;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IACrD,CAAC;IAEO,YAAY;QAClB,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO;QAC9B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6C,CAAC;YACxE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO;YAChD,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBACtC,IAAI,OAAO,EAAE,KAAK,QAAQ;oBAAE,SAAS;gBACrC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;oBAAE,SAAS;gBAClD,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;oBAAE,SAAS;gBAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,MAAM,CAAC;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC7E,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7C,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;IAC1C,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,qBAA6B,EAC7B,OAAe,EACf,SAAiB,EACjB,mBAA8D;IAE9D,MAAM,iBAAiB,GAAG,mBAAmB;SAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;SACvF,IAAI,EAAE,CAAC;IACV,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,MAAM,CAAC,KAAK,qBAAqB,IAAI,CAAC,CAAC;IACzC,CAAC,CAAC,MAAM,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC;IAC3B,CAAC,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/E,KAAK,MAAM,KAAK,IAAI,iBAAiB;QAAE,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IAChE,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-05-12T03:
|
|
5
|
-
"instarVersion": "0.28.
|
|
4
|
+
"generatedAt": "2026-05-12T03:32:09.889Z",
|
|
5
|
+
"instarVersion": "0.28.94",
|
|
6
6
|
"entryCount": 188,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: minor -->
|
|
4
|
+
<!-- Valid values: patch, minor, major -->
|
|
5
|
+
<!-- patch = bug fixes, refactors, test additions, doc updates -->
|
|
6
|
+
<!-- minor = new features, new APIs, new capabilities (backwards-compatible) -->
|
|
7
|
+
<!-- major = breaking changes to existing APIs or behavior -->
|
|
8
|
+
|
|
9
|
+
## What Changed
|
|
10
|
+
|
|
11
|
+
### Project-scope Phase 1b PR 2 — drift cache + cost ledger
|
|
12
|
+
|
|
13
|
+
Second PR of project-scope Phase 1b. Adds two persistent-state
|
|
14
|
+
primitives the drift checker has been operating without since v0.28.93:
|
|
15
|
+
|
|
16
|
+
- **Verdict cache** — `ProjectDriftCheckerCache`. 24-hour TTL,
|
|
17
|
+
mtime-fast-path before computing the cache key, disk-backed
|
|
18
|
+
snapshot at `.instar/drift-verdict-cache.json` so the cache
|
|
19
|
+
survives restarts. When file mtimes match the recorded entry and
|
|
20
|
+
template version + model id are unchanged, the prior verdict is
|
|
21
|
+
returned without re-hashing or calling the model. When content
|
|
22
|
+
changes, the cache key shifts and the LLM is consulted again.
|
|
23
|
+
|
|
24
|
+
- **Cost ledger** — `DriftSpendLedger`. Daily-rotated append-only
|
|
25
|
+
JSONL at `.instar/drift-spend-YYYY-MM-DD.jsonl` enforcing the
|
|
26
|
+
spec's $1/day per-agent spend cap. Strict-greater-than boundary
|
|
27
|
+
(equal-to-cap is allowed; one cent over is not). Pre-reservation
|
|
28
|
+
rows are append-only and reconciled with the actual spend after
|
|
29
|
+
the call. Lock-coordinated on `.instar/local/drift-spend.lock`
|
|
30
|
+
via proper-lockfile (same convention as AgentRegistry and
|
|
31
|
+
SharedStateLedger). 30-day retention; older files are pruned
|
|
32
|
+
through `SafeFsExecutor.safeRmSync`.
|
|
33
|
+
|
|
34
|
+
Both are optional fields on `ProjectDriftChecker`'s config. The
|
|
35
|
+
v0.28.93 contract — neither cache nor ledger — is preserved
|
|
36
|
+
exactly when neither is passed. Wiring into the round runner
|
|
37
|
+
(consumer) ships in the next PR.
|
|
38
|
+
|
|
39
|
+
## What to Tell Your User
|
|
40
|
+
|
|
41
|
+
- **Drift checks won't burn budget**: I now have a daily spending cap
|
|
42
|
+
on the model calls that power drift checks, plus a cache so I don't
|
|
43
|
+
pay for the same check twice. You don't have to do anything; it just
|
|
44
|
+
works. If I ever hit the daily limit, I escalate to you rather than
|
|
45
|
+
silently overspending.
|
|
46
|
+
|
|
47
|
+
## Summary of New Capabilities
|
|
48
|
+
|
|
49
|
+
- `ProjectDriftCheckerCache` class — sha256-keyed verdict cache with
|
|
50
|
+
24-hour TTL, mtime fast-path, and disk-backed snapshot at
|
|
51
|
+
`.instar/drift-verdict-cache.json`. Avoids redundant LLM calls when
|
|
52
|
+
spec + referenced files are unchanged.
|
|
53
|
+
- `DriftSpendLedger` class — daily-rotated JSONL cost ledger
|
|
54
|
+
(`.instar/drift-spend-YYYY-MM-DD.jsonl`) enforcing a $1-per-agent
|
|
55
|
+
per-UTC-day spend cap. Lock-coordinated via proper-lockfile. Exposes
|
|
56
|
+
`reserve()`, `reconcile()`, `spentToday()`, `pruneOlderThan()`,
|
|
57
|
+
and the `OverBudgetError` thrown class.
|
|
58
|
+
- `ProjectDriftChecker` config now accepts optional `cache`, `ledger`,
|
|
59
|
+
and `estimatedCostPerCallUsd` fields. Backwards-compatible: passing
|
|
60
|
+
neither preserves the v0.28.93 contract.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Side-Effects Review — project-scope Phase 1b PR 2 (Drift cache + cost ledger)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `project-scope-phase1b-pr2`
|
|
4
|
+
**Date:** `2026-05-11`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `required (new persistent-state + cost-control surface)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Second PR of Phase 1b. Ships the two persistent-state primitives the drift
|
|
11
|
+
checker has been operating without since PR 1:
|
|
12
|
+
|
|
13
|
+
- A verdict cache with mtime fast-path that avoids redundant LLM calls
|
|
14
|
+
when the spec and its referenced files haven't changed.
|
|
15
|
+
- A daily-rotated cost ledger that enforces the $1/day per-agent
|
|
16
|
+
spend ceiling the spec requires.
|
|
17
|
+
|
|
18
|
+
Both classes are usable on their own; `ProjectDriftChecker` now accepts
|
|
19
|
+
them as optional config and wires them in around the LLM call. When
|
|
20
|
+
neither is passed (the PR 1 contract), behavior is unchanged.
|
|
21
|
+
|
|
22
|
+
The `POST /projects/:id/drift-check` HTTP endpoint and the per-project
|
|
23
|
+
mutex around it ship in PR 3 alongside the round runner. PR 2 is the
|
|
24
|
+
data-plane piece; PR 3 is the routing.
|
|
25
|
+
|
|
26
|
+
Spec source: `docs/specs/PROJECT-SCOPE-SPEC.md` § Phase 1.4 ("Cache key",
|
|
27
|
+
mtime fast-path, "Cost ceiling").
|
|
28
|
+
|
|
29
|
+
New files:
|
|
30
|
+
- `src/core/ProjectDriftCheckerCache.ts` (~230 lines) — sha256-keyed
|
|
31
|
+
in-memory verdict cache. Mtime fast-path checks file mtimes against
|
|
32
|
+
the recorded entry before computing the cache key; full-hash path is
|
|
33
|
+
fallback. 24h TTL. Disk-backed snapshot at
|
|
34
|
+
`.instar/drift-verdict-cache.json` so the cache survives restarts
|
|
35
|
+
without a cold first-call after sleep/wake. Snapshot is corruption-
|
|
36
|
+
tolerant (corrupt file → start fresh, will be overwritten on next put).
|
|
37
|
+
- `src/core/DriftSpendLedger.ts` (~240 lines) — daily-rotated append-only
|
|
38
|
+
JSONL ledger at `.instar/drift-spend-YYYY-MM-DD.jsonl`. Each `reserve()`
|
|
39
|
+
appends a `{recordId, projectId, estimatedCost, actualCost: null,
|
|
40
|
+
timestamp}` row under an advisory file lock on
|
|
41
|
+
`.instar/local/drift-spend.lock` (proper-lockfile, same options as
|
|
42
|
+
the rest of the codebase). `reconcile(recordId, actualCost)` appends
|
|
43
|
+
a superseding row. `spentToday()` returns the de-duplicated tally.
|
|
44
|
+
`pruneOlderThan()` removes files outside the 30-day retention window
|
|
45
|
+
(via `SafeFsExecutor.safeRmSync`, NOT raw `fs.rmSync`).
|
|
46
|
+
- `tests/unit/ProjectDriftCheckerCache.test.ts` (13 cases) — hit/miss,
|
|
47
|
+
mtime fast-path (match, content-change miss, set-shrink invalidation),
|
|
48
|
+
cache-key invalidation on every input (templateVersion, modelId,
|
|
49
|
+
specBody, file content, file rename), TTL expiry, invalidate(),
|
|
50
|
+
disk persistence across instances, corrupt-snapshot tolerance,
|
|
51
|
+
`computeCacheKey` determinism + instability semantics.
|
|
52
|
+
- `tests/unit/DriftSpendLedger.test.ts` (13 cases) — default cap matches
|
|
53
|
+
spec, reserve + tally, strict-greater-than boundary at the cap
|
|
54
|
+
(equal-to-cap allowed), reconcile supersedes (no double-count),
|
|
55
|
+
idempotent reconcile, negative/non-finite rejected, JSONL row count
|
|
56
|
+
+ shape after reserve+reconcile, lock-coordinated `spentToday`,
|
|
57
|
+
concurrent reserves serialize, `pruneOlderThan` removes old files,
|
|
58
|
+
corrupt row tolerance, `OverBudgetError` exposes cap/spent/estimated.
|
|
59
|
+
|
|
60
|
+
Modified files:
|
|
61
|
+
- `src/core/ProjectDriftChecker.ts` (~+80 lines) — config accepts
|
|
62
|
+
optional `cache` and `ledger`. Order of operations inside `run()`:
|
|
63
|
+
(1) all input validation (unchanged from PR 1) → (2) cache lookup
|
|
64
|
+
(skip LLM on hit) → (3) ledger reserve (return over-budget on cap)
|
|
65
|
+
→ (4) LLM call → (5) on success, cache.put + ledger.reconcile.
|
|
66
|
+
LLM-failure verdicts (schema-fail, timeout) are NOT cached; input-
|
|
67
|
+
validation verdicts (over-budget, deleted-files, path-jail-fail) are
|
|
68
|
+
cheap to recompute and don't benefit from caching.
|
|
69
|
+
- `tests/unit/ProjectDriftChecker.test.ts` (+5 cases) — cache hit avoids
|
|
70
|
+
LLM, content change forces re-run, over-budget short-circuits before
|
|
71
|
+
the LLM, reserve+reconcile round-trip, LLM-failure not cached.
|
|
72
|
+
|
|
73
|
+
## Decision-point inventory
|
|
74
|
+
|
|
75
|
+
- **Cache hit short-circuit** (`ProjectDriftCheckerCache.lookup`) —
|
|
76
|
+
**add** — on hit, returns the cached verdict directly, bypassing
|
|
77
|
+
the LLM. Cache key is `sha256(promptTemplateVersion + modelId +
|
|
78
|
+
specBodySha + sortedFileHashes)`; mtime fast-path bypasses the hash
|
|
79
|
+
but checks `(templateVersion, modelId, all file mtimes)`. Cache
|
|
80
|
+
invariants — TTL, hash key on file content, mtime equality — are
|
|
81
|
+
exercised by 13 unit tests.
|
|
82
|
+
- **Daily cap rejection** (`DriftSpendLedger.reserve` → throws
|
|
83
|
+
`OverBudgetError`) — **add** — if `spent + estimated > cap`, throw.
|
|
84
|
+
`ProjectDriftChecker` catches it and returns
|
|
85
|
+
`manual-review-required(over-budget)`. Strict greater-than boundary
|
|
86
|
+
matches the spec (iter-2 correction).
|
|
87
|
+
- **Verdict NOT cached on LLM failure** (`run` post-call) — **add** —
|
|
88
|
+
schema-fail / timeout / failed-citation-verification all return
|
|
89
|
+
before the cache write; only `no-drift`, `minor-drift`,
|
|
90
|
+
`premise-violated` reach the put path. A second call gets a fresh
|
|
91
|
+
LLM attempt rather than the previously-cached failure verdict.
|
|
92
|
+
|
|
93
|
+
All three decisions are conservative: hit-on-content-match (never
|
|
94
|
+
hit on stale content), reject-at-strict-cap (never overshoot), no
|
|
95
|
+
cached failure (always re-try).
|
|
96
|
+
|
|
97
|
+
## Over-block vs under-block analysis
|
|
98
|
+
|
|
99
|
+
### Cache
|
|
100
|
+
|
|
101
|
+
Over-block risk: returning a stale verdict. The cache key includes the
|
|
102
|
+
full sha256 of every relevant input, so a stale verdict can only be
|
|
103
|
+
served if (a) file content is unchanged AND (b) within TTL AND (c)
|
|
104
|
+
modelId + templateVersion match. The mtime fast-path only fires when
|
|
105
|
+
the recorded mtimes match the current stat() results — `touch` without
|
|
106
|
+
content change does NOT cause a hit; it forces a full-hash fall-back
|
|
107
|
+
that then matches (correct outcome). The 24h TTL is the durable floor
|
|
108
|
+
even when nothing else moves.
|
|
109
|
+
|
|
110
|
+
Under-block risk: cache miss when it should have hit. Acceptable —
|
|
111
|
+
correctness costs an extra LLM call. The mtime fast-path is a
|
|
112
|
+
performance optimization, not a correctness invariant.
|
|
113
|
+
|
|
114
|
+
### Ledger
|
|
115
|
+
|
|
116
|
+
Over-block risk: rejecting a call that wouldn't actually exceed the
|
|
117
|
+
cap. Pre-reserve uses the `estimatedCostPerCallUsd` constant (default
|
|
118
|
+
$0.01) rather than the call's real cost; in the worst case we
|
|
119
|
+
over-reserve and over-block. Acceptable because the cap is the AGENT
|
|
120
|
+
ceiling, not a hard per-call limit. Reconcile lowers the recorded
|
|
121
|
+
spend back to actual, so subsequent calls aren't penalized.
|
|
122
|
+
|
|
123
|
+
Under-block risk: under-counting. The strict `>` boundary lets
|
|
124
|
+
`spent + estimated == cap` pass; that's the documented behavior, not
|
|
125
|
+
a bug. A reservation that's never reconciled (caller crashed) stays as
|
|
126
|
+
the pending estimate and contributes to the cap until UTC rollover —
|
|
127
|
+
fail-safe rather than fail-open.
|
|
128
|
+
|
|
129
|
+
The cap is per-machine. On multi-machine setups, the spec documents
|
|
130
|
+
the worst-case as `N × cap` per day; the true atomic cross-machine
|
|
131
|
+
cap is a same-PR tracked deferred child (`drift-spend-cross-machine`)
|
|
132
|
+
referenced in the spec. PR 2 does not change that posture.
|
|
133
|
+
|
|
134
|
+
## Signal vs authority audit
|
|
135
|
+
|
|
136
|
+
Neither primitive holds authority. They're persistence + cost-control
|
|
137
|
+
plumbing. The drift verdict is still a signal; the cache returns a
|
|
138
|
+
prior signal, the ledger throws on overshoot but the resulting verdict
|
|
139
|
+
(`manual-review-required(over-budget)`) is again just a signal. The
|
|
140
|
+
round-runner in PR 3 holds authority for round-start, combining drift
|
|
141
|
+
+ artifact checks.
|
|
142
|
+
|
|
143
|
+
## Interactions with existing systems
|
|
144
|
+
|
|
145
|
+
- **`ProjectDriftChecker`.** Both fields are optional — passing
|
|
146
|
+
neither preserves the PR 1 contract exactly. Passing one but not
|
|
147
|
+
the other is supported (cache-only / ledger-only).
|
|
148
|
+
- **`proper-lockfile`.** Already a dep (used by AgentRegistry,
|
|
149
|
+
SharedStateLedger, PlatformActivityRegistry). Same `LOCK_OPTIONS`
|
|
150
|
+
shape, same stale-detection convention.
|
|
151
|
+
- **`.instar/local/`.** Lock file lives under this machine-local
|
|
152
|
+
subdirectory by spec — same convention as `round-runner.lock` (the
|
|
153
|
+
PR 3 chokepoint). Not git-synced, so two machines don't fight over
|
|
154
|
+
a 0-byte lockfile in the sync layer.
|
|
155
|
+
- **`SafeFsExecutor`.** Used for `pruneOlderThan` rather than direct
|
|
156
|
+
`fs.rmSync`, matching the lint-no-direct-destructive contract.
|
|
157
|
+
- **Backup / snapshot.** The spec lists
|
|
158
|
+
`.instar/drift-spend-*.jsonl` in the snapshot-include set (so a
|
|
159
|
+
restore can see the prior day's spend); this PR creates the files
|
|
160
|
+
that the existing snapshot job already names — no snapshot code
|
|
161
|
+
change needed.
|
|
162
|
+
|
|
163
|
+
## Rollback cost
|
|
164
|
+
|
|
165
|
+
- **Cache.** Revert restores PR 1 behavior. The snapshot file at
|
|
166
|
+
`.instar/drift-verdict-cache.json` becomes orphaned but harmless
|
|
167
|
+
(no other code reads it).
|
|
168
|
+
- **Ledger.** Revert restores PR 1 behavior. The JSONL files at
|
|
169
|
+
`.instar/drift-spend-*.jsonl` are orphaned; the existing
|
|
170
|
+
snapshot/cleanup tooling still sweeps them via retention rules.
|
|
171
|
+
- **No schema migrations.** Both primitives are file-backed,
|
|
172
|
+
forward-compatible. Older agent versions that don't know about
|
|
173
|
+
these fields ignore them.
|
|
174
|
+
|
|
175
|
+
## What this PR explicitly defers
|
|
176
|
+
|
|
177
|
+
Per spec § Phase 1.4 and § Phase 1.5, these belong to drift but ship
|
|
178
|
+
in PR 3 (round runner) alongside the consumer that needs them:
|
|
179
|
+
|
|
180
|
+
- `POST /projects/:id/drift-check` HTTP endpoint with per-project
|
|
181
|
+
mutex. PR 2 ships the primitive; PR 3 ships the route + server
|
|
182
|
+
wiring (cache + ledger instantiation on `AgentServer` startup,
|
|
183
|
+
injection into the routes ctx, mutex map keyed by `projectId`).
|
|
184
|
+
- Integration into `ProjectRoundRunner.preflight()` step 10 ("Drift
|
|
185
|
+
check: load cached verdict if fresh + present + all hashes match;
|
|
186
|
+
otherwise run drift").
|
|
187
|
+
- Multi-machine "documented worst case is `N × cap` per day"
|
|
188
|
+
visibility — surfacing the per-machine spend in the digest or
|
|
189
|
+
dashboard.
|
|
190
|
+
|
|
191
|
+
## Verification
|
|
192
|
+
|
|
193
|
+
- `npm run lint` — passes (tsc + lint-no-direct-destructive).
|
|
194
|
+
- `npx vitest run tests/unit/ProjectDriftChecker.test.ts
|
|
195
|
+
tests/unit/DriftSpendLedger.test.ts
|
|
196
|
+
tests/unit/ProjectDriftCheckerCache.test.ts` — 76/76 pass.
|
|
197
|
+
- PR 1's 45 existing tests still green; the 5 new cache+ledger
|
|
198
|
+
integration tests assert the contract changes.
|