@juli-os/ledger 0.1.7 → 0.3.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.
@@ -16,6 +16,7 @@ export interface Workflow {
16
16
  readonly project: string;
17
17
  readonly path: string;
18
18
  readonly threadId: string | null;
19
+ readonly tenant: string;
19
20
  readonly meta: JsonRecord;
20
21
  readonly createdAt: string;
21
22
  readonly updatedAt: string;
package/dist/index.d.ts CHANGED
@@ -4,5 +4,3 @@ export { TERMINAL_WORKFLOW_STATUSES, STEP_KINDS, AGENT_STEP_KINDS, INSTRUCTABLE_
4
4
  export { createUsageStore, type UsageRecord, type UsageStats, type TimelinePoint, type UsageStore, } from './platform/ledger/usage.ts';
5
5
  export { findPrice, priceUsage, type TierWhen, type PriceTier, type ModelPrice, PRICING_SOURCE, PRICING_RETRIEVED_AT, BIGMODEL_PRICES, type UsageRow, type CostBreakdown, type ModelUsageCost, type UsageCost, } from './platform/ledger/pricing.ts';
6
6
  export { emptyCaseFile, parseCaseFile, serializeCaseFile, withOriginal, appendSection, appendExecution, appendDecision, appendReference, originalEmail, type CaseDecision, type ArtifactRef, type CaseFileDoc, } from './platform/ledger/casefile.ts';
7
- export { rfc3339, systemClock, randomIds, type Clock, type IdGen } from './platform/shared/clock.ts';
8
- export { asRecord, safeParse, type JsonRecord, type JsonValue } from './platform/shared/json.ts';
package/dist/index.js CHANGED
@@ -241,6 +241,7 @@ var rowToWorkflow = (r) => ({
241
241
  project: str(r["project"]),
242
242
  path: str(r["path"]),
243
243
  threadId: r["thread_id"] === null ? null : str(r["thread_id"]),
244
+ tenant: str(r["tenant"]) || "internal",
244
245
  meta: asRecord(safeParse(str(r["meta"]))),
245
246
  createdAt: str(r["created_at"]),
246
247
  updatedAt: str(r["updated_at"])
@@ -307,7 +308,11 @@ var createLedgerStore = (dbPath, deps = {}) => {
307
308
  if (!wfCols.includes("thread_id")) {
308
309
  db.exec("ALTER TABLE workflows ADD COLUMN thread_id TEXT");
309
310
  }
311
+ if (!wfCols.includes("tenant")) {
312
+ db.exec("ALTER TABLE workflows ADD COLUMN tenant TEXT NOT NULL DEFAULT 'internal'");
313
+ }
310
314
  db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_thread ON workflows(thread_id)");
315
+ db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant, created_at)");
311
316
  const stepCols = db.prepare("PRAGMA table_info(workflow_steps)").all().map((c) => c.name);
312
317
  if (!stepCols.includes("started_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN started_at TEXT");
313
318
  if (!stepCols.includes("completed_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN completed_at TEXT");
@@ -349,19 +354,36 @@ var createLedgerStore = (dbPath, deps = {}) => {
349
354
  const id = ids.newId("wf");
350
355
  const ts = now();
351
356
  db.prepare(
352
- `INSERT INTO workflows (id, parent_id, kind, title, status, owner, meta, created_at, updated_at)
353
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
354
- ).run(id, w.parentId ?? null, w.kind, w.title, w.status ?? "running", w.owner ?? null, JSON.stringify(w.meta ?? {}), ts, ts);
357
+ `INSERT INTO workflows (id, parent_id, kind, title, status, owner, tenant, meta, created_at, updated_at)
358
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
359
+ ).run(id, w.parentId ?? null, w.kind, w.title, w.status ?? "running", w.owner ?? null, w.tenant || "internal", JSON.stringify(w.meta ?? {}), ts, ts);
355
360
  return getWorkflowById(id);
356
361
  },
357
362
  getWorkflow(id) {
358
363
  return getWorkflowById(id);
359
364
  },
360
- listWorkflows(limit = 100, status) {
365
+ listWorkflows(limit = 100, status, tenant) {
366
+ const where = [];
367
+ const params = [];
361
368
  if (status !== void 0 && status !== "") {
362
- return db.prepare("SELECT * FROM workflows WHERE status=? ORDER BY created_at DESC, rowid DESC LIMIT ?").all(status, limit).map(rowToWorkflow);
369
+ where.push("status=?");
370
+ params.push(status);
371
+ }
372
+ if (tenant !== void 0 && tenant !== "") {
373
+ where.push("tenant=?");
374
+ params.push(tenant);
363
375
  }
364
- return db.prepare("SELECT * FROM workflows ORDER BY created_at DESC, rowid DESC LIMIT ?").all(limit).map(rowToWorkflow);
376
+ const sql = where.length > 0 ? `SELECT * FROM workflows WHERE ${where.join(" AND ")} ORDER BY created_at DESC, rowid DESC LIMIT ?` : "SELECT * FROM workflows ORDER BY created_at DESC, rowid DESC LIMIT ?";
377
+ return db.prepare(sql).all(...params, limit).map(rowToWorkflow);
378
+ },
379
+ setWorkflowTenant(id, tenant) {
380
+ const next = tenant === "" ? "internal" : tenant;
381
+ const cur = getWorkflowById(id);
382
+ if (cur === null || cur.tenant === next) return;
383
+ db.prepare("UPDATE workflows SET tenant=?, updated_at=? WHERE id=?").run(next, now(), id);
384
+ db.prepare(
385
+ "INSERT INTO events (entity_type, entity_id, type, payload, ts) VALUES ('workflow', ?, 'tenant_changed', ?, ?)"
386
+ ).run(id, JSON.stringify({ from: cur.tenant, to: next }), now());
365
387
  },
366
388
  setWorkflowSession(id, session) {
367
389
  db.prepare("UPDATE workflows SET session=?, updated_at=? WHERE id=?").run(session, now(), id);
@@ -523,7 +545,18 @@ var createLedgerStore = (dbPath, deps = {}) => {
523
545
  ).run(now(), now(), id);
524
546
  return Number(res.changes) === 1;
525
547
  },
526
- listSteps(status, limit = 100) {
548
+ listSteps(status, limit = 100, tenant) {
549
+ if (tenant !== void 0 && tenant !== "") {
550
+ return db.prepare(
551
+ `SELECT st.id AS id, st.workflow_id AS workflow_id, st.seq AS seq, st.kind AS kind,
552
+ st.title AS title, st.status AS status,
553
+ COALESCE(st.input,'') AS input, COALESCE(st.output,'') AS output,
554
+ COALESCE(st.summary,'') AS summary, st.updated_at AS updated_at,
555
+ st.started_at AS started_at, st.completed_at AS completed_at
556
+ FROM workflow_steps st JOIN workflows w ON w.id = st.workflow_id
557
+ WHERE st.status=? AND w.tenant=? ORDER BY st.updated_at DESC LIMIT ?`
558
+ ).all(status, tenant, limit).map(rowToStep);
559
+ }
527
560
  return db.prepare(
528
561
  `SELECT ${STEP_COLS} FROM workflow_steps WHERE status=? ORDER BY updated_at DESC LIMIT ?`
529
562
  ).all(status, limit).map(rowToStep);
@@ -1290,7 +1323,6 @@ export {
1290
1323
  appendExecution,
1291
1324
  appendReference,
1292
1325
  appendSection,
1293
- asRecord,
1294
1326
  createLedgerStore,
1295
1327
  createUsageStore,
1296
1328
  emptyCaseFile,
@@ -1298,10 +1330,6 @@ export {
1298
1330
  originalEmail,
1299
1331
  parseCaseFile,
1300
1332
  priceUsage,
1301
- randomIds,
1302
- rfc3339,
1303
- safeParse,
1304
1333
  serializeCaseFile,
1305
- systemClock,
1306
1334
  withOriginal
1307
1335
  };
@@ -199,6 +199,7 @@ var rowToWorkflow = (r) => ({
199
199
  project: str(r["project"]),
200
200
  path: str(r["path"]),
201
201
  threadId: r["thread_id"] === null ? null : str(r["thread_id"]),
202
+ tenant: str(r["tenant"]) || "internal",
202
203
  meta: asRecord(safeParse(str(r["meta"]))),
203
204
  createdAt: str(r["created_at"]),
204
205
  updatedAt: str(r["updated_at"])
@@ -265,7 +266,11 @@ var createLedgerStore = (dbPath, deps = {}) => {
265
266
  if (!wfCols.includes("thread_id")) {
266
267
  db.exec("ALTER TABLE workflows ADD COLUMN thread_id TEXT");
267
268
  }
269
+ if (!wfCols.includes("tenant")) {
270
+ db.exec("ALTER TABLE workflows ADD COLUMN tenant TEXT NOT NULL DEFAULT 'internal'");
271
+ }
268
272
  db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_thread ON workflows(thread_id)");
273
+ db.exec("CREATE INDEX IF NOT EXISTS idx_workflows_tenant ON workflows(tenant, created_at)");
269
274
  const stepCols = db.prepare("PRAGMA table_info(workflow_steps)").all().map((c) => c.name);
270
275
  if (!stepCols.includes("started_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN started_at TEXT");
271
276
  if (!stepCols.includes("completed_at")) db.exec("ALTER TABLE workflow_steps ADD COLUMN completed_at TEXT");
@@ -307,19 +312,36 @@ var createLedgerStore = (dbPath, deps = {}) => {
307
312
  const id = ids.newId("wf");
308
313
  const ts = now();
309
314
  db.prepare(
310
- `INSERT INTO workflows (id, parent_id, kind, title, status, owner, meta, created_at, updated_at)
311
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
312
- ).run(id, w.parentId ?? null, w.kind, w.title, w.status ?? "running", w.owner ?? null, JSON.stringify(w.meta ?? {}), ts, ts);
315
+ `INSERT INTO workflows (id, parent_id, kind, title, status, owner, tenant, meta, created_at, updated_at)
316
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
317
+ ).run(id, w.parentId ?? null, w.kind, w.title, w.status ?? "running", w.owner ?? null, w.tenant || "internal", JSON.stringify(w.meta ?? {}), ts, ts);
313
318
  return getWorkflowById(id);
314
319
  },
315
320
  getWorkflow(id) {
316
321
  return getWorkflowById(id);
317
322
  },
318
- listWorkflows(limit = 100, status) {
323
+ listWorkflows(limit = 100, status, tenant) {
324
+ const where = [];
325
+ const params = [];
319
326
  if (status !== void 0 && status !== "") {
320
- return db.prepare("SELECT * FROM workflows WHERE status=? ORDER BY created_at DESC, rowid DESC LIMIT ?").all(status, limit).map(rowToWorkflow);
327
+ where.push("status=?");
328
+ params.push(status);
329
+ }
330
+ if (tenant !== void 0 && tenant !== "") {
331
+ where.push("tenant=?");
332
+ params.push(tenant);
321
333
  }
322
- return db.prepare("SELECT * FROM workflows ORDER BY created_at DESC, rowid DESC LIMIT ?").all(limit).map(rowToWorkflow);
334
+ const sql = where.length > 0 ? `SELECT * FROM workflows WHERE ${where.join(" AND ")} ORDER BY created_at DESC, rowid DESC LIMIT ?` : "SELECT * FROM workflows ORDER BY created_at DESC, rowid DESC LIMIT ?";
335
+ return db.prepare(sql).all(...params, limit).map(rowToWorkflow);
336
+ },
337
+ setWorkflowTenant(id, tenant) {
338
+ const next = tenant === "" ? "internal" : tenant;
339
+ const cur = getWorkflowById(id);
340
+ if (cur === null || cur.tenant === next) return;
341
+ db.prepare("UPDATE workflows SET tenant=?, updated_at=? WHERE id=?").run(next, now(), id);
342
+ db.prepare(
343
+ "INSERT INTO events (entity_type, entity_id, type, payload, ts) VALUES ('workflow', ?, 'tenant_changed', ?, ?)"
344
+ ).run(id, JSON.stringify({ from: cur.tenant, to: next }), now());
323
345
  },
324
346
  setWorkflowSession(id, session) {
325
347
  db.prepare("UPDATE workflows SET session=?, updated_at=? WHERE id=?").run(session, now(), id);
@@ -481,7 +503,18 @@ var createLedgerStore = (dbPath, deps = {}) => {
481
503
  ).run(now(), now(), id);
482
504
  return Number(res.changes) === 1;
483
505
  },
484
- listSteps(status, limit = 100) {
506
+ listSteps(status, limit = 100, tenant) {
507
+ if (tenant !== void 0 && tenant !== "") {
508
+ return db.prepare(
509
+ `SELECT st.id AS id, st.workflow_id AS workflow_id, st.seq AS seq, st.kind AS kind,
510
+ st.title AS title, st.status AS status,
511
+ COALESCE(st.input,'') AS input, COALESCE(st.output,'') AS output,
512
+ COALESCE(st.summary,'') AS summary, st.updated_at AS updated_at,
513
+ st.started_at AS started_at, st.completed_at AS completed_at
514
+ FROM workflow_steps st JOIN workflows w ON w.id = st.workflow_id
515
+ WHERE st.status=? AND w.tenant=? ORDER BY st.updated_at DESC LIMIT ?`
516
+ ).all(status, tenant, limit).map(rowToStep);
517
+ }
485
518
  return db.prepare(
486
519
  `SELECT ${STEP_COLS} FROM workflow_steps WHERE status=? ORDER BY updated_at DESC LIMIT ?`
487
520
  ).all(status, limit).map(rowToStep);
@@ -12,11 +12,13 @@ export interface LifecycleStore {
12
12
  parentId?: string;
13
13
  status?: WorkflowStatus;
14
14
  owner?: string;
15
+ tenant?: string;
15
16
  }): Workflow;
16
17
  getWorkflow(id: string): Workflow | null;
17
- listWorkflows(limit?: number, status?: string): readonly Workflow[];
18
+ listWorkflows(limit?: number, status?: string, tenant?: string): readonly Workflow[];
18
19
  setWorkflowStatus(id: string, status: WorkflowStatus): void;
19
20
  setWorkflowSession(id: string, session: string): void;
21
+ setWorkflowTenant(id: string, tenant: string): void;
20
22
  updateWorkflowMeta(id: string, patch: JsonRecord): JsonRecord;
21
23
  setWorkflowRouting(id: string, session: string, project: string): void;
22
24
  assignThread(workflowId: string, threadId: string): void;
@@ -64,7 +66,7 @@ export interface LifecycleStore {
64
66
  setStepOutput(id: string, patch: JsonRecord): void;
65
67
  appendStepOutputItem(id: string, key: string, entry: JsonValue, cap: number): void;
66
68
  claimRunningStep(id: string): boolean;
67
- listSteps(status: StepStatus, limit?: number): readonly Step[];
69
+ listSteps(status: StepStatus, limit?: number, tenant?: string): readonly Step[];
68
70
  runningAgentStepForSession(session: string): Step | null;
69
71
  sessionHasOpenWork(session: string, excludeWorkflowId?: string): boolean;
70
72
  appendEvent(entityType: 'workflow' | 'step' | 'judgment' | 'agent', entityId: string, type: string, payload?: JsonRecord): LedgerEvent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juli-os/ledger",
3
- "version": "0.1.7",
3
+ "version": "0.3.0",
4
4
  "description": "juli-os L1 layer: the ledger of record for agent work — workflows, steps, case files, and token usage in one zero-dependency SQLite layer.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,11 +27,15 @@
27
27
  "build": "tsc -p tsconfig.build.json && esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js && esbuild bin/juli-ledger.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/juli-ledger.js && chmod +x dist/juli-ledger.js && python3 scripts/postbuild.py",
28
28
  "test": "node --test \"test/*.test.ts\"",
29
29
  "smoke:dist": "node --input-type=module -e \"import('./dist/index.js').then(async m => { const { join } = await import('node:path'); const { tmpdir } = await import('node:os'); const s = m.createLedgerStore(join(tmpdir(), 'peel-dist-' + Date.now() + '.db')); const wf = s.createWorkflow({kind:'case',title:'dist smoke'}); if (!s.getWorkflow(wf.id)) process.exit(1); console.log('DIST SMOKE OK'); })\"",
30
+ "docs:gen": "node scripts/gen-docs.mjs && typedoc",
31
+ "docs:build": "npm run docs:gen && mkdocs build --strict",
30
32
  "prepublishOnly": "npm run build"
31
33
  },
32
34
  "devDependencies": {
33
35
  "@types/node": "^22.0.0",
34
36
  "esbuild": "^0.24.0",
37
+ "typedoc": "^0.28.20",
38
+ "typedoc-plugin-markdown": "^4.14.1",
35
39
  "typescript": "^5.6.0"
36
40
  },
37
41
  "publishConfig": {