ferrycat 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/demo.js ADDED
@@ -0,0 +1,340 @@
1
+ /**
2
+ * FerryCat Zero-Config Sandbox Demo (REQ-EXP-01).
3
+ *
4
+ * Runs a complete contract lifecycle — dispatch -> claim -> supervised execution -> TOCTOU
5
+ * re-verification -> COMPLETED — entirely inside an isolated temporary sandbox, so a brand-new
6
+ * user can see the deterministic handoff loop without configuring a single MCP server.
7
+ *
8
+ * Design constraints:
9
+ * - Zero pollution: every artifact (sample source, handler script, SQLite database) lives under
10
+ * `os.tmpdir()/ferrycat-demo-*`. The user's real workspace and `~/.ferrycat/mesh.db` are never touched.
11
+ * - No simulation shortcuts on the engine path: the demo drives the exact same public APIs a real
12
+ * daemon uses (`dispatchContract` / `pollNextTask` / `executeTaskAndFulfill` / `verifyContract`),
13
+ * so a green demo is genuine evidence rather than a puppet show.
14
+ * - Machine-readable mode: with `json: true` the function writes nothing to stdout and returns a
15
+ * plain object, keeping CI/upstream automation parseable.
16
+ */
17
+ import fs from 'node:fs';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
20
+ import { FerryCatEngine } from './engine.js';
21
+ import { executeTaskAndFulfill } from './supervisor.js';
22
+ const TOTAL_STEPS = 4;
23
+ const SAMPLE_REL_PATH = 'src/calc.ts';
24
+ /**
25
+ * Sample source under review. Deliberately contains exactly one advisory-level defect
26
+ * (`==` instead of `===`) so the demo handler has something real to find and the verdict
27
+ * is deterministic across runs and platforms.
28
+ */
29
+ const SAMPLE_SOURCE = `export function add(a: number, b: number): number {
30
+ return a + b;
31
+ }
32
+
33
+ export function divide(a: number, b: number): number {
34
+ if (b == 0) {
35
+ throw new Error('Division by zero');
36
+ }
37
+ return a / b;
38
+ }
39
+
40
+ export const VERSION = '1.0.0';
41
+ `;
42
+ /**
43
+ * Handler script written into the sandbox. It reads the contract from stdin (same protocol as
44
+ * scripts/review-handler.js), inspects the real pointer file on disk, then emits a delivery
45
+ * envelope on stdout. `execDelayMs` keeps the child alive long enough for the supervisor
46
+ * heartbeat ticker to fire at least once, so the demo can prove lease renewal is live.
47
+ */
48
+ function buildHandlerScript(execDelayMs) {
49
+ return `/**
50
+ * FerryCat demo handler (generated by \`ferrycat-cli demo\`).
51
+ * Reads the contract JSON from stdin, inspects the pointer file, prints a delivery envelope.
52
+ */
53
+ const fs = require('node:fs');
54
+ const path = require('node:path');
55
+
56
+ const EXEC_DELAY_MS = ${execDelayMs};
57
+
58
+ function inspect(workspaceRoot, pointers) {
59
+ const issues = [];
60
+ for (const pointer of pointers || []) {
61
+ const absPath = path.resolve(workspaceRoot, pointer.path);
62
+ if (!fs.existsSync(absPath)) {
63
+ issues.push(pointer.path + ': file is missing on disk');
64
+ continue;
65
+ }
66
+ const lines = fs.readFileSync(absPath, 'utf8').split(/\\r?\\n/);
67
+ lines.forEach((line, idx) => {
68
+ // Loose equality on a numeric/string guard is the classic review catch.
69
+ if (/(^|[^=!<>])==([^=]|$)/.test(line)) {
70
+ issues.push(pointer.path + ':' + (idx + 1) + " uses '==' instead of '===' (strict equality)");
71
+ }
72
+ });
73
+ }
74
+ return issues;
75
+ }
76
+
77
+ function main() {
78
+ const raw = fs.readFileSync(0, 'utf8');
79
+ if (!raw.trim()) {
80
+ console.error('demo handler: no contract payload received on stdin');
81
+ process.exit(1);
82
+ }
83
+ const contract = JSON.parse(raw);
84
+ const workspaceRoot = process.env.FERRYCAT_WORKSPACE_ROOT || process.cwd();
85
+ const issues = inspect(workspaceRoot, contract.pointers);
86
+
87
+ const finish = () => {
88
+ process.stdout.write(
89
+ JSON.stringify({
90
+ ok: true,
91
+ summary:
92
+ 'Reviewed ' + (contract.pointers || []).length + ' file(s) for contract ' +
93
+ contract.contract_id + '; ' + issues.length + ' advisory finding(s).',
94
+ structured_output: {
95
+ passed: true,
96
+ issues,
97
+ },
98
+ }) + '\\n'
99
+ );
100
+ };
101
+
102
+ if (EXEC_DELAY_MS > 0) {
103
+ setTimeout(finish, EXEC_DELAY_MS);
104
+ } else {
105
+ finish();
106
+ }
107
+ }
108
+
109
+ main();
110
+ `;
111
+ }
112
+ function makePainter(enabled) {
113
+ const wrap = (code) => (text) => enabled ? `\u001b[${code}m${text}\u001b[0m` : text;
114
+ return {
115
+ bold: wrap('1'),
116
+ dim: wrap('2'),
117
+ green: wrap('32'),
118
+ cyan: wrap('36'),
119
+ yellow: wrap('33'),
120
+ magenta: wrap('35'),
121
+ gray: wrap('90'),
122
+ red: wrap('31'),
123
+ };
124
+ }
125
+ function shortSha(sha) {
126
+ return sha.length <= 12 ? sha : `${sha.slice(0, 8)}...${sha.slice(-4)}`;
127
+ }
128
+ function removeSandbox(sandbox) {
129
+ // Windows keeps WAL/SHM handles briefly after close(); retries absorb that window.
130
+ fs.rmSync(sandbox, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
131
+ }
132
+ export async function runDemo(options = {}) {
133
+ const startedAt = Date.now();
134
+ const execDelayMs = Math.max(0, options.execDelayMs ?? 1900);
135
+ const execTtlSec = Math.max(2, options.execTtlSec ?? 5);
136
+ const human = !options.json;
137
+ const colorOn = options.color ?? (human && Boolean(process.stdout.isTTY) && !process.env.NO_COLOR);
138
+ const paint = makePainter(colorOn);
139
+ const write = options.write ?? ((line) => process.stdout.write(`${line}\n`));
140
+ const say = (line = '') => {
141
+ if (human)
142
+ write(line);
143
+ };
144
+ const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'ferrycat-demo-'));
145
+ let engine;
146
+ let succeeded = false;
147
+ const steps = [];
148
+ const warnings = [];
149
+ let heartbeatRenewals = 0;
150
+ let contractId = '';
151
+ let claimToken = '';
152
+ let status = 'UNKNOWN';
153
+ let verdict = 'PASSED';
154
+ let issues = [];
155
+ let dirtyFiles = 0;
156
+ const record = (index, title, fields) => {
157
+ steps.push({ index, total: TOTAL_STEPS, title, fields });
158
+ say('');
159
+ say(` ${paint.green('✔')} ${paint.bold(`[${index}/${TOTAL_STEPS}] ${title}`)}`);
160
+ for (const [key, value] of Object.entries(fields)) {
161
+ say(` ${paint.gray(key.padEnd(10))}${value}`);
162
+ }
163
+ };
164
+ try {
165
+ // ---------------------------------------------------------------------
166
+ // Sandbox scaffolding (isolated workspace + handler + SQLite database)
167
+ // ---------------------------------------------------------------------
168
+ const srcDir = path.join(sandbox, 'src');
169
+ fs.mkdirSync(srcDir, { recursive: true });
170
+ fs.writeFileSync(path.join(sandbox, SAMPLE_REL_PATH), SAMPLE_SOURCE, 'utf8');
171
+ const handlerPath = path.join(sandbox, 'demo-handler.cjs');
172
+ fs.writeFileSync(handlerPath, buildHandlerScript(execDelayMs), 'utf8');
173
+ engine = new FerryCatEngine({ dbPath: path.join(sandbox, 'demo-mesh.db') });
174
+ say('');
175
+ say(paint.bold('⚡ FerryCat Zero-Config Sandbox Demo ⚡'));
176
+ say(`${paint.gray('Sandbox:')} ${sandbox}`);
177
+ say(paint.gray('No MCP config. No daemon. Your real workspace and mesh.db are untouched.'));
178
+ // ---------------------------------------------------------------------
179
+ // [1/4] Dispatch — Antigravity locks the pointer fingerprint
180
+ // ---------------------------------------------------------------------
181
+ const dispatchRes = engine.dispatchContract({
182
+ requester_agent: 'antigravity',
183
+ target_agent: 'workbuddy',
184
+ task_type: 'code_review',
185
+ title: 'Review calc.ts for correctness and strict-equality hygiene',
186
+ context_summary: 'Sandbox demo contract. Inspect src/calc.ts and report any correctness or ' +
187
+ 'strict-equality violations. This contract is generated by `ferrycat-cli demo`.',
188
+ workspace_root: sandbox,
189
+ pointers: [{ path: SAMPLE_REL_PATH }],
190
+ contract_schema: {
191
+ type: 'object',
192
+ properties: {
193
+ passed: { type: 'boolean' },
194
+ issues: { type: 'array', items: { type: 'string' } },
195
+ },
196
+ required: ['passed', 'issues'],
197
+ additionalProperties: false,
198
+ },
199
+ exec_ttl_sec: execTtlSec,
200
+ });
201
+ contractId = dispatchRes.contract_id;
202
+ const lockedFile = dispatchRes.files[0];
203
+ if (dispatchRes.warning) {
204
+ warnings.push(dispatchRes.warning);
205
+ }
206
+ record(1, 'Contract Dispatched', {
207
+ ID: contractId,
208
+ Actor: `antigravity -> workbuddy ${paint.dim('(task: code_review)')}`,
209
+ Locked: `${lockedFile.path} ${paint.dim(`(sha256: ${shortSha(lockedFile.sha256)})`)}`,
210
+ Status: `${paint.cyan('SUBMITTED')} ${paint.dim('-> queue')}`,
211
+ });
212
+ // ---------------------------------------------------------------------
213
+ // [2/4] Claim — WorkBuddy polls, verifies fingerprints, wins the CAS lease
214
+ // ---------------------------------------------------------------------
215
+ const pollRes = await engine.pollNextTask({ agent_id: 'workbuddy' });
216
+ if (!pollRes.has_task || !pollRes.contract || pollRes.status !== 'CLAIMED') {
217
+ throw new Error(`Demo claim failed: expected CLAIMED, got has_task=${pollRes.has_task} status=${pollRes.status ?? 'undefined'}. ` +
218
+ `${pollRes.message ?? ''}`.trim());
219
+ }
220
+ claimToken = pollRes.contract.claim_token ?? '';
221
+ const claimed = pollRes.contract;
222
+ record(2, 'Contract Claimed', {
223
+ Holder: claimed.target_agent,
224
+ Lease: claimToken || paint.yellow('(none)'),
225
+ Status: `${paint.dim('SUBMITTED ->')} ${paint.yellow('CLAIMED')}`,
226
+ Verify: `${paint.green('fingerprints matched')} ${paint.dim('(0 dirty files)')}`,
227
+ });
228
+ // ---------------------------------------------------------------------
229
+ // [3/4] Supervised execution — real child process, heartbeat, schema gate
230
+ // ---------------------------------------------------------------------
231
+ const execResult = await executeTaskAndFulfill(engine, claimed, { execFile: handlerPath }, {
232
+ onEvent: (event) => {
233
+ if (event.event === 'HEARTBEAT_RENEWED') {
234
+ heartbeatRenewals += 1;
235
+ }
236
+ },
237
+ });
238
+ if (!execResult.ok) {
239
+ throw new Error(`Supervised execution failed: ${execResult.error ?? execResult.status}`);
240
+ }
241
+ // The authoritative delivery lives on the row, not on the fulfill ack.
242
+ const finalState = engine.inspectMeshState({ contract_id: contractId });
243
+ const finalRow = finalState.contracts[0] ?? {};
244
+ status = String(finalRow.status ?? 'UNKNOWN');
245
+ const structured = finalRow.structured_output;
246
+ if (structured && Array.isArray(structured.issues)) {
247
+ issues = structured.issues.map((item) => String(item));
248
+ }
249
+ verdict = issues.length > 0 ? 'CHANGES_REQUESTED' : 'PASSED';
250
+ record(3, 'Execution & Schema Check', {
251
+ Handler: `${path.basename(handlerPath)} ${paint.dim('(sandboxed child process)')}`,
252
+ Schema: `${paint.green('passed Ajv validation')}`,
253
+ Heartbeat: heartbeatRenewals > 0
254
+ ? `${paint.green(`${heartbeatRenewals} lease renewal(s)`)}`
255
+ : paint.dim('no renewal needed (short task)'),
256
+ Verdict: verdict === 'PASSED'
257
+ ? paint.green('PASSED')
258
+ : paint.yellow(`CHANGES_REQUESTED ${paint.dim(`(${issues.length} advisory)`)}`),
259
+ });
260
+ // ---------------------------------------------------------------------
261
+ // [4/4] Delivery — second TOCTOU pass then terminal COMPLETED
262
+ // ---------------------------------------------------------------------
263
+ const verifyRes = engine.verifyContract({
264
+ contract_id: contractId,
265
+ agent_id: 'workbuddy',
266
+ });
267
+ dirtyFiles = verifyRes.dirty_files.length;
268
+ if (!verifyRes.valid) {
269
+ throw new Error(`Demo TOCTOU re-verification failed: ${verifyRes.dirty_files.length} dirty file(s).`);
270
+ }
271
+ if (status !== 'COMPLETED') {
272
+ throw new Error(`Demo expected terminal status COMPLETED, got ${status}.`);
273
+ }
274
+ record(4, 'Contract Completed', {
275
+ TOCTOU: `${paint.green(`fingerprints matched (${dirtyFiles} dirty files)`)}`,
276
+ Final: `${paint.green(paint.bold('COMPLETED'))} ${paint.dim(`(elapsed: ${Date.now() - startedAt}ms)`)}`,
277
+ Findings: issues.length === 0
278
+ ? paint.dim('none')
279
+ : issues.map((item) => paint.dim(item)).join(`\n${' '.repeat(14)}`),
280
+ });
281
+ succeeded = true;
282
+ return {
283
+ ok: true,
284
+ sandbox,
285
+ sandbox_kept: Boolean(options.keep),
286
+ contract_id: contractId,
287
+ claim_token: claimToken,
288
+ status,
289
+ verdict,
290
+ issues,
291
+ dirty_files: dirtyFiles,
292
+ heartbeat_renewals: heartbeatRenewals,
293
+ elapsed_ms: Date.now() - startedAt,
294
+ steps,
295
+ warnings,
296
+ };
297
+ }
298
+ finally {
299
+ if (engine) {
300
+ try {
301
+ engine.close();
302
+ }
303
+ catch {
304
+ // Closing is best-effort; the sandbox removal below is the real cleanup contract.
305
+ }
306
+ }
307
+ const retainSandbox = Boolean(options.keep || !succeeded);
308
+ if (retainSandbox) {
309
+ if (options.keep) {
310
+ say('');
311
+ say(`${paint.gray('Sandbox kept at:')} ${sandbox}`);
312
+ say(paint.gray(' Inspect it with: sqlite3 "<sandbox>/demo-mesh.db" ".tables"'));
313
+ }
314
+ }
315
+ else {
316
+ try {
317
+ removeSandbox(sandbox);
318
+ }
319
+ catch (err) {
320
+ warnings.push(`Sandbox cleanup failed (${err instanceof Error ? err.message : String(err)}); remove "${sandbox}" manually.`);
321
+ }
322
+ }
323
+ if (succeeded) {
324
+ say('');
325
+ say(paint.green(paint.bold(' 🎉 End-to-end multi-agent handoff verified successfully!')));
326
+ say('');
327
+ say(paint.bold(' Next steps:'));
328
+ say(` ${paint.gray('•')} Add to your IDE: ${paint.cyan('npx -y ferrycat')} ${paint.dim('(MCP server entry in mcpServers)')}`);
329
+ say(` ${paint.gray('•')} Inspect contracts: ${paint.cyan('npx ferrycat inspect --target workbuddy')}`);
330
+ say(` ${paint.gray('•')} Keep a sandbox: ${paint.cyan('npx ferrycat demo --keep')}`);
331
+ say(` ${paint.gray('•')} Full CLI help: ${paint.cyan('npx ferrycat --help')}`);
332
+ }
333
+ else {
334
+ say('');
335
+ say(paint.red(' ✖ Demo did not reach a terminal COMPLETED state.'));
336
+ say(paint.gray(` Sandbox retained for debugging: ${sandbox}`));
337
+ }
338
+ }
339
+ }
340
+ //# sourceMappingURL=demo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"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"}
package/dist/engine.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DispatchContractInput, DispatchContractResult, ExtendTtlInput, ExtendTtlResult, FailContractInput, FailContractResult, FulfillContractInput, FulfillContractResult, InspectMeshStateInput, PollNextTaskInput, PollNextTaskResult, ReleaseContractInput, ReleaseContractResult, TaskType, VerifyContractInput, VerifyContractResult } from './types.js';
1
+ import type { AcquireDaemonLeaseInput, AcquireDaemonLeaseResult, DaemonLockRow, DispatchContractInput, DispatchContractResult, ExtendTtlInput, ExtendTtlResult, FailContractInput, FailContractResult, FulfillContractInput, FulfillContractResult, InspectMeshStateInput, PollNextTaskInput, PollNextTaskResult, ReleaseContractInput, ReleaseContractResult, ReleaseDaemonLeaseInput, ReleaseDaemonLeaseResult, RenewDaemonLeaseInput, RenewDaemonLeaseResult, TaskType, VerifyContractInput, VerifyContractResult } from './types.js';
2
2
  /** Default queue window (SUBMITTED) in seconds. */
3
3
  export declare const DEFAULT_QUEUE_TTL_SEC = 1800;
4
4
  /**
@@ -85,6 +85,30 @@ export declare class FerryCatEngine {
85
85
  * (created_at + LIFETIME_QUEUE_TTL_MULTIPLIER * queue_ttl_sec).
86
86
  */
87
87
  extendTtl(input: ExtendTtlInput, nowMs?: number): ExtendTtlResult;
88
+ /**
89
+ * Read-only lease snapshot used by the execution supervisor (Phase 3 Step 0, CK-03) to
90
+ * re-read row state after an optimistic-lock conflict instead of crashing the worker.
91
+ * Never mutates state.
92
+ */
93
+ getLeaseSnapshot(contractId: string): {
94
+ status: string;
95
+ claim_token: string | null;
96
+ claimed_at: number | null;
97
+ exec_ttl_sec: number;
98
+ extend_count: number;
99
+ } | null;
100
+ /**
101
+ * Persist partial worker output into salvaged_result_json WITHOUT any status transition
102
+ * (Phase 3 Step 0, CK-07 / CK-09). Allowed for the lease holder while CLAIMED, and also
103
+ * right after the window closed (TIMED_OUT) so produced output is never silently dropped.
104
+ * Returns false when the caller no longer owns the lease (no write happens).
105
+ */
106
+ recordSalvage(input: {
107
+ contract_id: string;
108
+ agent_id?: string;
109
+ claim_token?: string;
110
+ salvage: Record<string, unknown>;
111
+ }): boolean;
88
112
  /**
89
113
  * CLAIMED -> FAILED (terminal). Lets the holder close the loop explicitly instead of
90
114
  * leaving the requester waiting for the execution TTL to expire.
@@ -103,5 +127,9 @@ export declare class FerryCatEngine {
103
127
  total_returned: number;
104
128
  contracts: Array<Record<string, unknown>>;
105
129
  };
130
+ acquireDaemonLease(input: AcquireDaemonLeaseInput): AcquireDaemonLeaseResult;
131
+ renewDaemonLease(input: RenewDaemonLeaseInput): RenewDaemonLeaseResult;
132
+ releaseDaemonLease(input: ReleaseDaemonLeaseInput): ReleaseDaemonLeaseResult;
133
+ inspectDaemonLease(lockId?: string): DaemonLockRow | null;
106
134
  close(): void;
107
135
  }