@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.
- package/.turbo/turbo-build.log +14 -14
- package/CHANGELOG.md +22 -0
- package/dist/api/object-storage.d.ts.map +1 -1
- package/dist/api/object-storage.js +28 -2
- package/dist/api/object-storage.js.map +1 -1
- package/dist/core/append-logs.d.ts.map +1 -1
- package/dist/core/append-logs.js +1 -1
- package/dist/core/append-logs.js.map +1 -1
- package/dist/core/reap-stale-open-streams.d.ts.map +1 -1
- package/dist/core/reap-stale-open-streams.js +7 -0
- package/dist/core/reap-stale-open-streams.js.map +1 -1
- package/dist/core/retention.d.ts.map +1 -1
- package/dist/core/retention.js +25 -0
- package/dist/core/retention.js.map +1 -1
- package/dist/core/session/parse-session.d.ts +1 -1
- package/dist/core/session/parse-session.d.ts.map +1 -1
- package/dist/core/session/parse-session.js.map +1 -1
- package/dist/temporal/activities/compact-stream.d.ts.map +1 -1
- package/dist/temporal/activities/compact-stream.js +17 -1
- package/dist/temporal/activities/compact-stream.js.map +1 -1
- package/dist/temporal/activities/compaction-reconcile.d.ts.map +1 -1
- package/dist/temporal/activities/compaction-reconcile.js +7 -0
- package/dist/temporal/activities/compaction-reconcile.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +17164 -3306
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -30
- package/src/api/object-storage.ts +21 -2
- package/src/core/append-logs.ts +1 -1
- package/src/core/reap-stale-open-streams.ts +2 -0
- package/src/core/retention.ts +5 -0
- package/src/core/session/parse-session.ts +1 -1
- package/src/presentation/routes/append-logs.test.ts +2 -3
- package/src/temporal/activities/compact-stream.ts +13 -1
- package/src/temporal/activities/compaction-reconcile.ts +2 -0
- package/test/fixtures/lease-token.ts +31 -27
- 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": "
|
|
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": "
|
|
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": "
|
|
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.
|
|
39
|
-
"@shipfox/node-module": "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.
|
|
44
|
-
|
|
45
|
-
|
|
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(() =>
|
|
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(() =>
|
|
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
|
}
|
package/src/core/append-logs.ts
CHANGED
|
@@ -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
|
|
package/src/core/retention.ts
CHANGED
|
@@ -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: [
|
|
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(() =>
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
};
|