@shipfox/api-logs 6.0.0 → 8.0.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.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +14 -14
  2. package/CHANGELOG.md +22 -0
  3. package/dist/api/object-storage.d.ts.map +1 -1
  4. package/dist/api/object-storage.js +28 -2
  5. package/dist/api/object-storage.js.map +1 -1
  6. package/dist/core/append-logs.d.ts.map +1 -1
  7. package/dist/core/append-logs.js +1 -1
  8. package/dist/core/append-logs.js.map +1 -1
  9. package/dist/core/reap-stale-open-streams.d.ts.map +1 -1
  10. package/dist/core/reap-stale-open-streams.js +7 -0
  11. package/dist/core/reap-stale-open-streams.js.map +1 -1
  12. package/dist/core/retention.d.ts.map +1 -1
  13. package/dist/core/retention.js +25 -0
  14. package/dist/core/retention.js.map +1 -1
  15. package/dist/core/session/parse-session.d.ts +1 -1
  16. package/dist/core/session/parse-session.d.ts.map +1 -1
  17. package/dist/core/session/parse-session.js.map +1 -1
  18. package/dist/temporal/activities/compact-stream.d.ts.map +1 -1
  19. package/dist/temporal/activities/compact-stream.js +17 -1
  20. package/dist/temporal/activities/compact-stream.js.map +1 -1
  21. package/dist/temporal/activities/compaction-reconcile.d.ts.map +1 -1
  22. package/dist/temporal/activities/compaction-reconcile.js +7 -0
  23. package/dist/temporal/activities/compaction-reconcile.js.map +1 -1
  24. package/dist/temporal/workflows/index.bundle.js +17164 -3306
  25. package/dist/tsconfig.test.tsbuildinfo +1 -1
  26. package/package.json +9 -30
  27. package/src/api/object-storage.ts +21 -2
  28. package/src/core/append-logs.ts +1 -1
  29. package/src/core/reap-stale-open-streams.ts +2 -0
  30. package/src/core/retention.ts +5 -0
  31. package/src/core/session/parse-session.ts +1 -1
  32. package/src/presentation/routes/append-logs.test.ts +2 -3
  33. package/src/temporal/activities/compact-stream.ts +13 -1
  34. package/src/temporal/activities/compaction-reconcile.ts +2 -0
  35. package/test/fixtures/lease-token.ts +31 -27
  36. package/tsconfig.build.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-logs",
3
3
  "license": "MIT",
4
- "version": "6.0.0",
4
+ "version": "8.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -29,40 +29,19 @@
29
29
  "@temporalio/workflow": "1.18.1",
30
30
  "drizzle-orm": "^0.45.2",
31
31
  "zod": "^4.4.3",
32
- "@shipfox/api-auth-context": "6.0.0",
33
- "@shipfox/api-agent-dto": "6.0.0",
32
+ "@shipfox/api-auth-context": "7.1.0",
34
33
  "@shipfox/api-logs-dto": "5.0.0",
35
- "@shipfox/api-workflows-dto": "6.0.0",
34
+ "@shipfox/api-workflows-dto": "8.0.0",
36
35
  "@shipfox/config": "1.2.2",
37
36
  "@shipfox/node-drizzle": "0.3.2",
38
- "@shipfox/node-fastify": "0.2.4",
39
- "@shipfox/node-module": "0.4.0",
40
- "@shipfox/node-opentelemetry": "0.5.2",
37
+ "@shipfox/node-fastify": "0.3.0",
38
+ "@shipfox/node-module": "0.5.0",
41
39
  "@shipfox/node-outbox": "0.2.4",
40
+ "@shipfox/node-opentelemetry": "0.6.0",
42
41
  "@shipfox/node-postgres": "0.4.2",
43
- "@shipfox/node-temporal": "0.3.2"
44
- },
45
- "devDependencies": {
46
- "@temporalio/client": "1.18.1",
47
- "@temporalio/common": "1.18.1",
48
- "@temporalio/testing": "1.18.1",
49
- "@temporalio/worker": "1.18.1",
50
- "@types/pg": "^8.15.5",
51
- "drizzle-kit": "^0.31.10",
52
- "fastify": "^5.3.3",
53
- "fastify-type-provider-zod": "^6.0.0",
54
- "fishery": "^2.4.0",
55
- "@shipfox/node-auth-root-key": "0.2.0",
56
- "@shipfox/api-auth": "6.0.0",
57
- "@shipfox/api-auth-dto": "6.0.0",
58
- "@shipfox/inter-module": "0.2.0",
59
- "@shipfox/biome": "1.8.2",
60
- "@shipfox/depcruise": "1.0.2",
61
- "@shipfox/node-jwt": "0.3.0",
62
- "@shipfox/swc": "1.2.6",
63
- "@shipfox/ts-config": "1.3.8",
64
- "@shipfox/typescript": "1.1.7",
65
- "@shipfox/vitest": "1.2.3"
42
+ "@shipfox/node-temporal": "0.4.0",
43
+ "@shipfox/node-error-monitoring": "0.2.0",
44
+ "@shipfox/workflow-document": "2.1.1"
66
45
  },
67
46
  "scripts": {
68
47
  "build": "shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js",
@@ -9,6 +9,8 @@ import {
9
9
  } from '@aws-sdk/client-s3';
10
10
  import {Upload} from '@aws-sdk/lib-storage';
11
11
  import {getSignedUrl} from '@aws-sdk/s3-request-presigner';
12
+ import {reportError} from '@shipfox/node-error-monitoring';
13
+ import {logger} from '@shipfox/node-opentelemetry';
12
14
  import {config} from '#config.js';
13
15
  import {type LogObjectKeyParams, logObjectKey} from '#core/entities/log-object.js';
14
16
 
@@ -140,12 +142,29 @@ export async function putCompactedObject(params: PutCompactedObjectParams): Prom
140
142
  params.signal.addEventListener(
141
143
  'abort',
142
144
  () => {
143
- void upload.abort().catch(() => undefined);
145
+ void upload.abort().catch((error) => {
146
+ logger().error(
147
+ {err: error, objectKey: params.key},
148
+ 'Failed to abort multipart log upload',
149
+ );
150
+ reportError(error, {
151
+ boundary: 'logs.cleanup',
152
+ operation: 'abort-multipart-upload',
153
+ extra: {objectKey: params.key},
154
+ });
155
+ });
144
156
  },
145
157
  {once: true},
146
158
  );
147
159
  if (params.signal.aborted) {
148
- await upload.abort().catch(() => undefined);
160
+ await upload.abort().catch((error) => {
161
+ logger().error({err: error, objectKey: params.key}, 'Failed to abort multipart log upload');
162
+ reportError(error, {
163
+ boundary: 'logs.cleanup',
164
+ operation: 'abort-multipart-upload',
165
+ extra: {objectKey: params.key},
166
+ });
167
+ });
149
168
  throw new Error('Compaction upload aborted before it started');
150
169
  }
151
170
  }
@@ -1,5 +1,4 @@
1
1
  import {Buffer} from 'node:buffer';
2
- import {DEFAULT_HARNESS, type Harness} from '@shipfox/api-agent-dto';
3
2
  import {
4
3
  type LogRecord,
5
4
  parseLogRecordLine,
@@ -8,6 +7,7 @@ import {
8
7
  } from '@shipfox/api-logs-dto';
9
8
  import type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';
10
9
  import {logger} from '@shipfox/node-opentelemetry';
10
+ import {DEFAULT_HARNESS, type Harness} from '@shipfox/workflow-document';
11
11
  import {config} from '#config.js';
12
12
  import {accrueStoredBytes, claimCap, ensureJobAccounting, isJobCapped} from '#db/accounting.js';
13
13
  import {insertChunk} from '#db/chunks.js';
@@ -1,3 +1,4 @@
1
+ import {reportError} from '@shipfox/node-error-monitoring';
1
2
  import {logger} from '@shipfox/node-opentelemetry';
2
3
  import {db} from '#db/db.js';
3
4
  import {listStaleOpenStreams} from '#db/streams.js';
@@ -53,6 +54,7 @@ export async function reapStaleOpenStreams(
53
54
  } catch (error) {
54
55
  result.failed += 1;
55
56
  logger().error({err: error, streamId: stream.id}, 'Failed to reap stale open log stream');
57
+ reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});
56
58
  }
57
59
  }
58
60
 
@@ -1,3 +1,4 @@
1
+ import {reportError} from '@shipfox/node-error-monitoring';
1
2
  import {logger} from '@shipfox/node-opentelemetry';
2
3
  import {deleteObjectsByPrefix} from '#api/object-storage.js';
3
4
  import {config} from '#config.js';
@@ -116,6 +117,7 @@ export async function runRetentionSweep(
116
117
  {err: error, streamId: stream.id},
117
118
  'Failed to delete expired log stream objects',
118
119
  );
120
+ reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});
119
121
  continue;
120
122
  }
121
123
 
@@ -129,6 +131,7 @@ export async function runRetentionSweep(
129
131
  {err: error, streamId: stream.id},
130
132
  'Failed to delete expired log stream row',
131
133
  );
134
+ reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});
132
135
  continue;
133
136
  }
134
137
 
@@ -143,6 +146,7 @@ export async function runRetentionSweep(
143
146
  {err: error, streamId: stream.id},
144
147
  'Failed to reload raced expired log stream',
145
148
  );
149
+ reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});
146
150
  continue;
147
151
  }
148
152
  if (current) {
@@ -159,6 +163,7 @@ export async function runRetentionSweep(
159
163
  {err: error, streamId: stream.id},
160
164
  'Failed to delete raced expired log stream',
161
165
  );
166
+ reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});
162
167
  continue;
163
168
  }
164
169
  }
@@ -1,5 +1,5 @@
1
- import type {Harness} from '@shipfox/api-agent-dto';
2
1
  import type {SessionViewRow} from '@shipfox/api-logs-dto';
2
+ import type {Harness} from '@shipfox/workflow-document';
3
3
  import {parseClaudeSessionRecord} from './claude-parser.js';
4
4
  import {parsePiSessionRecord} from './pi-parser.js';
5
5
  import type {AgentSessionRecord} from './session-record.js';
@@ -1,8 +1,7 @@
1
1
  import {Buffer} from 'node:buffer';
2
- import {createLeaseTokenAuthMethod} from '@shipfox/api-auth';
3
2
  import {AUTH_USER} from '@shipfox/api-auth-context';
4
3
  import {type AuthMethod, closeApp, createApp, type FastifyInstance} from '@shipfox/node-fastify';
5
- import {mintLeaseToken} from '#test/fixtures/lease-token.js';
4
+ import {fakeLeaseTokenAuthMethod, mintLeaseToken} from '#test/fixtures/lease-token.js';
6
5
  import {endLine, ndjsonBody, outputLine, recordLine} from '#test/fixtures/ndjson.js';
7
6
  import {createTestWorkflowsClient} from '#test/fixtures/workflows-client.js';
8
7
  import {createLogsRoutes} from './index.js';
@@ -36,7 +35,7 @@ describe('POST /runs/jobs/current/steps/:stepId/logs', () => {
36
35
 
37
36
  beforeAll(async () => {
38
37
  app = await createApp({
39
- auth: [createLeaseTokenAuthMethod(), stubUserAuth],
38
+ auth: [fakeLeaseTokenAuthMethod, stubUserAuth],
40
39
  routes: createLogsRoutes(createTestWorkflowsClient()),
41
40
  swagger: false,
42
41
  });
@@ -1,3 +1,5 @@
1
+ import {reportError} from '@shipfox/node-error-monitoring';
2
+ import {logger} from '@shipfox/node-opentelemetry';
1
3
  import {Context} from '@temporalio/activity';
2
4
  import {compactedObjectKey, deleteObject, putCompactedObject} from '#api/object-storage.js';
3
5
  import {compactedGzipStream} from '#core/compaction.js';
@@ -79,7 +81,17 @@ async function compactStream(
79
81
  stats.lastSeq !== expected.maxSeq ||
80
82
  stats.uncompressedBytes !== expected.uncompressedBytes
81
83
  ) {
82
- await deleteObject(uploadKey).catch(() => undefined);
84
+ await deleteObject(uploadKey).catch((error) => {
85
+ logger().error(
86
+ {err: error, streamId: stream.id, objectKey: uploadKey},
87
+ 'Failed to delete invalid compacted log object',
88
+ );
89
+ reportError(error, {
90
+ boundary: 'logs.cleanup',
91
+ operation: 'delete-invalid-compaction-object',
92
+ extra: {streamId: stream.id, objectKey: uploadKey},
93
+ });
94
+ });
83
95
  throw new Error(
84
96
  `Compaction integrity check failed for stream ${stream.id}: streamed ${stats.chunkCount} chunks / ${stats.uncompressedBytes} bytes up to seq ${stats.lastSeq}, table holds ${expected.count} / ${expected.uncompressedBytes} bytes up to seq ${expected.maxSeq}`,
85
97
  );
@@ -1,3 +1,4 @@
1
+ import {reportError} from '@shipfox/node-error-monitoring';
1
2
  import {logger} from '@shipfox/node-opentelemetry';
2
3
  import {temporalClient} from '@shipfox/node-temporal';
3
4
  import {config} from '#config.js';
@@ -41,6 +42,7 @@ export async function compactionReconcileActivity(): Promise<{restarted: number;
41
42
  {err: error, streamId: stream.id},
42
43
  'Failed to re-drive stale stream compaction',
43
44
  );
45
+ reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});
44
46
  }
45
47
  }
46
48
 
@@ -1,9 +1,8 @@
1
- import {JOB_LEASE_TOKEN_AUDIENCE} from '@shipfox/api-auth-dto';
2
- import {jobLeaseTokenKey} from '@shipfox/node-auth-root-key';
3
- import {signHs256} from '@shipfox/node-jwt';
1
+ import {AUTH_LEASED_JOB, setLeasedJobContext} from '@shipfox/api-auth-context';
2
+ import {JOB_LEASE_TOKEN_AUDIENCE, type JobLeaseTokenClaims} from '@shipfox/api-auth-dto';
3
+ import {type AuthMethod, ClientError, extractBearerToken} from '@shipfox/node-fastify';
4
4
 
5
- // Matches test/env.ts; the lease-token auth method reads this same value from config.
6
- const SECRET = jobLeaseTokenKey();
5
+ const leaseClaims = new Map<string, JobLeaseTokenClaims>();
7
6
 
8
7
  export interface MintLeaseTokenParams {
9
8
  jobId: string;
@@ -14,30 +13,35 @@ export interface MintLeaseTokenParams {
14
13
  workflowRunAttemptId?: string;
15
14
  currentStepId?: string;
16
15
  currentStepAttempt?: number;
17
- secret?: string;
18
- expiresIn?: string;
19
- audience?: string;
20
16
  }
21
17
 
22
18
  export function mintLeaseToken(params: MintLeaseTokenParams): Promise<string> {
23
- return signHs256({
24
- payload: {
25
- jobId: params.jobId,
26
- jobExecutionId: params.jobExecutionId,
27
- workflowRunId: params.workflowRunId ?? crypto.randomUUID(),
28
- workflowRunAttemptId: params.workflowRunAttemptId ?? crypto.randomUUID(),
29
- projectId: params.projectId ?? crypto.randomUUID(),
30
- workspaceId: params.workspaceId ?? crypto.randomUUID(),
31
- runnerSessionId: crypto.randomUUID(),
32
- ...(params.currentStepId && params.currentStepAttempt !== undefined
33
- ? {
34
- currentStepId: params.currentStepId,
35
- currentStepAttempt: params.currentStepAttempt,
36
- }
37
- : {}),
38
- },
39
- secret: params.secret ?? SECRET,
40
- expiresIn: params.expiresIn ?? '90m',
41
- audience: params.audience ?? JOB_LEASE_TOKEN_AUDIENCE,
19
+ const token = `test-auth-${crypto.randomUUID()}`;
20
+ leaseClaims.set(token, {
21
+ jobId: params.jobId,
22
+ jobExecutionId: params.jobExecutionId,
23
+ workflowRunId: params.workflowRunId ?? crypto.randomUUID(),
24
+ workflowRunAttemptId: params.workflowRunAttemptId ?? crypto.randomUUID(),
25
+ projectId: params.projectId ?? crypto.randomUUID(),
26
+ workspaceId: params.workspaceId ?? crypto.randomUUID(),
27
+ runnerSessionId: crypto.randomUUID(),
28
+ ...(params.currentStepId && params.currentStepAttempt !== undefined
29
+ ? {currentStepId: params.currentStepId, currentStepAttempt: params.currentStepAttempt}
30
+ : {}),
31
+ aud: JOB_LEASE_TOKEN_AUDIENCE,
32
+ iat: 0,
33
+ exp: Number.MAX_SAFE_INTEGER,
42
34
  });
35
+ return Promise.resolve(token);
43
36
  }
37
+
38
+ export const fakeLeaseTokenAuthMethod: AuthMethod = {
39
+ name: AUTH_LEASED_JOB,
40
+ authenticate: (request) => {
41
+ const token = extractBearerToken(request.headers.authorization);
42
+ const claims = token ? leaseClaims.get(token) : undefined;
43
+ if (!claims) throw new ClientError('Authentication required', 'unauthorized', {status: 401});
44
+ setLeasedJobContext(request, claims);
45
+ return Promise.resolve();
46
+ },
47
+ };