@shipfox/api-logs-dto 10.2.0 → 16.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 +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/events.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inter-module.d.ts +132 -0
- package/dist/inter-module.d.ts.map +1 -0
- package/dist/inter-module.js +54 -0
- package/dist/inter-module.js.map +1 -0
- package/dist/schemas/append.d.ts +6 -1
- package/dist/schemas/append.d.ts.map +1 -1
- package/dist/schemas/append.js +4 -1
- package/dist/schemas/append.js.map +1 -1
- package/dist/schemas/index.d.ts +2 -2
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -2
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/read.d.ts +1 -1
- package/dist/schemas/read.d.ts.map +1 -1
- package/dist/schemas/read.js +1 -1
- package/dist/schemas/read.js.map +1 -1
- package/dist/schemas/record.d.ts +116 -27
- package/dist/schemas/record.d.ts.map +1 -1
- package/dist/schemas/record.js +9 -5
- package/dist/schemas/record.js.map +1 -1
- package/dist/schemas/session-view.d.ts +21 -21
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -2
- package/src/index.ts +4 -0
- package/src/inter-module.test.ts +69 -0
- package/src/inter-module.ts +69 -0
- package/src/schemas/append.ts +8 -1
- package/src/schemas/index.ts +4 -0
- package/src/schemas/read.ts +3 -1
- package/src/schemas/record.ts +12 -4
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-logs-dto",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "16.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -15,10 +15,15 @@
|
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./dist/index.d.ts",
|
|
17
17
|
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./inter-module": {
|
|
20
|
+
"types": "./dist/inter-module.d.ts",
|
|
21
|
+
"default": "./dist/inter-module.js"
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"dependencies": {
|
|
21
|
-
"zod": "^4.4.3"
|
|
25
|
+
"zod": "^4.4.3",
|
|
26
|
+
"@shipfox/inter-module": "0.2.3"
|
|
22
27
|
},
|
|
23
28
|
"imports": {
|
|
24
29
|
"#*": "./dist/*"
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,9 @@ export {
|
|
|
11
11
|
appendLogsQuerySchema,
|
|
12
12
|
appendLogsResponseSchema,
|
|
13
13
|
type LogRecord,
|
|
14
|
+
type LogWriterConflictResponseDto,
|
|
14
15
|
logRecordSchema,
|
|
16
|
+
logWriterConflictResponseSchema,
|
|
15
17
|
MAX_RECORD_DATA_BYTES,
|
|
16
18
|
MAX_RECORD_NAME_BYTES,
|
|
17
19
|
type OffsetGapResponseDto,
|
|
@@ -25,6 +27,7 @@ export {
|
|
|
25
27
|
readLogsQuerySchema,
|
|
26
28
|
readLogsResponseSchema,
|
|
27
29
|
SESSION_VIEW_VERSION,
|
|
30
|
+
type ServerLogRecord,
|
|
28
31
|
type SessionViewDto,
|
|
29
32
|
type SessionViewLifecycleRow,
|
|
30
33
|
type SessionViewMessageRow,
|
|
@@ -34,6 +37,7 @@ export {
|
|
|
34
37
|
type SessionViewThinkingRow,
|
|
35
38
|
type SessionViewToolCallRow,
|
|
36
39
|
type SessionViewToolResultRow,
|
|
40
|
+
serverLogRecordSchema,
|
|
37
41
|
sessionViewLifecycleRowSchema,
|
|
38
42
|
sessionViewMessageRowSchema,
|
|
39
43
|
sessionViewRawRowSchema,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {logsInterModuleContract} from './inter-module.js';
|
|
2
|
+
|
|
3
|
+
describe('logsInterModuleContract', () => {
|
|
4
|
+
test('accepts server-origin append commands', () => {
|
|
5
|
+
const input = logsInterModuleContract.methods.appendServerRecords.input.parse({
|
|
6
|
+
jobId: '00000000-0000-4000-8000-000000000001',
|
|
7
|
+
workspaceId: '00000000-0000-4000-8000-000000000002',
|
|
8
|
+
projectId: '00000000-0000-4000-8000-000000000003',
|
|
9
|
+
workflowRunAttemptId: '00000000-0000-4000-8000-000000000004',
|
|
10
|
+
stepId: '00000000-0000-4000-8000-000000000005',
|
|
11
|
+
attempt: 1,
|
|
12
|
+
records: [
|
|
13
|
+
{v: 1, ts: 1, type: 'output', stream: 'stdout', data: 'hello'},
|
|
14
|
+
{v: 1, ts: 1, type: 'group_start', group_id: 'g1', parent_group_id: null, name: 'call'},
|
|
15
|
+
{v: 1, ts: 1, type: 'group_end', group_id: 'g1'},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(input.attempt).toBe(1);
|
|
20
|
+
expect(input.records).toHaveLength(3);
|
|
21
|
+
expect(input.records[1]).toMatchObject({type: 'group_start'});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('rejects records outside the server-writable stored union', () => {
|
|
25
|
+
const result = logsInterModuleContract.methods.appendServerRecords.input.safeParse({
|
|
26
|
+
jobId: '00000000-0000-4000-8000-000000000001',
|
|
27
|
+
workspaceId: '00000000-0000-4000-8000-000000000002',
|
|
28
|
+
projectId: '00000000-0000-4000-8000-000000000003',
|
|
29
|
+
workflowRunAttemptId: '00000000-0000-4000-8000-000000000004',
|
|
30
|
+
stepId: '00000000-0000-4000-8000-000000000005',
|
|
31
|
+
attempt: 1,
|
|
32
|
+
records: [{v: 1, ts: 1, type: 'agent_session', data: 'raw entry, not yet normalized'}],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(result.success).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('rejects server-only tombstones', () => {
|
|
39
|
+
const result = logsInterModuleContract.methods.appendServerRecords.input.safeParse({
|
|
40
|
+
jobId: '00000000-0000-4000-8000-000000000001',
|
|
41
|
+
workspaceId: '00000000-0000-4000-8000-000000000002',
|
|
42
|
+
projectId: '00000000-0000-4000-8000-000000000003',
|
|
43
|
+
workflowRunAttemptId: '00000000-0000-4000-8000-000000000004',
|
|
44
|
+
stepId: '00000000-0000-4000-8000-000000000005',
|
|
45
|
+
attempt: 1,
|
|
46
|
+
records: [{v: 1, ts: 1, type: 'capped'}],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(result.success).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('keeps identity, attempt, and output bounds enforced', () => {
|
|
53
|
+
const input = {
|
|
54
|
+
jobId: '00000000-0000-4000-8000-000000000001',
|
|
55
|
+
workspaceId: '00000000-0000-4000-8000-000000000002',
|
|
56
|
+
projectId: '00000000-0000-4000-8000-000000000003',
|
|
57
|
+
workflowRunAttemptId: '00000000-0000-4000-8000-000000000004',
|
|
58
|
+
stepId: '00000000-0000-4000-8000-000000000005',
|
|
59
|
+
attempt: 1,
|
|
60
|
+
records: [],
|
|
61
|
+
};
|
|
62
|
+
const schema = logsInterModuleContract.methods.appendServerRecords;
|
|
63
|
+
|
|
64
|
+
expect(schema.input.safeParse({...input, attempt: 0}).success).toBe(false);
|
|
65
|
+
expect(schema.input.safeParse({...input, attempt: 2_147_483_648}).success).toBe(false);
|
|
66
|
+
expect(schema.input.safeParse({...input, jobId: 'not-a-uuid'}).success).toBe(false);
|
|
67
|
+
expect(schema.output.safeParse({committedLength: 0, capped: true}).success).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {defineInterModuleContract, type InterModuleClient} from '@shipfox/inter-module';
|
|
2
|
+
import {z} from 'zod';
|
|
3
|
+
import {serverLogRecordSchema} from './schemas/index.js';
|
|
4
|
+
|
|
5
|
+
const idSchema = z.string().uuid();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Producer-owned Logs commands used by synchronous callers. The only method
|
|
9
|
+
* today is the server-origin append for server-executed steps (the tool step
|
|
10
|
+
* executor): it writes already-normalized stored records through the same
|
|
11
|
+
* offset-CAS and budget pipeline as the lease-bound runner route, but with
|
|
12
|
+
* chunk `origin` `server` and a tail-derived CAS offset (the caller owns no
|
|
13
|
+
* spool cursor). This is a trusted internal boundary, not an authorization
|
|
14
|
+
* boundary: the caller must derive the identity fields from its execution
|
|
15
|
+
* context rather than pass through arbitrary external input.
|
|
16
|
+
*/
|
|
17
|
+
export const logsInterModuleContract = defineInterModuleContract({
|
|
18
|
+
module: 'logs',
|
|
19
|
+
methods: {
|
|
20
|
+
appendServerRecords: {
|
|
21
|
+
input: z.object({
|
|
22
|
+
jobId: idSchema,
|
|
23
|
+
workspaceId: idSchema,
|
|
24
|
+
projectId: idSchema,
|
|
25
|
+
workflowRunAttemptId: idSchema,
|
|
26
|
+
stepId: idSchema,
|
|
27
|
+
attempt: z
|
|
28
|
+
.number()
|
|
29
|
+
.int()
|
|
30
|
+
.min(1)
|
|
31
|
+
.max(2_147_483_647)
|
|
32
|
+
.describe('Attempt number of the step this batch belongs to.'),
|
|
33
|
+
/**
|
|
34
|
+
* Already-normalized server-writable stored records (the read union
|
|
35
|
+
* without server-only tombstones), serialized to whole newline-terminated
|
|
36
|
+
* NDJSON lines on ingest. Server-origin records skip the raw-to-stored
|
|
37
|
+
* normalization the runner path applies: they are stored verbatim. Callers should
|
|
38
|
+
* coalesce records into batches up to (but never over) `LOG_APPEND_BODY_LIMIT_BYTES`;
|
|
39
|
+
* an empty batch is reserved for a heartbeat. A server-origin writer owns its stream;
|
|
40
|
+
* it cannot be mixed with a lease-bound runner writer because their byte cursors differ.
|
|
41
|
+
*/
|
|
42
|
+
records: z.array(serverLogRecordSchema),
|
|
43
|
+
}),
|
|
44
|
+
output: z.object({
|
|
45
|
+
committedLength: z
|
|
46
|
+
.number()
|
|
47
|
+
.int()
|
|
48
|
+
.min(0)
|
|
49
|
+
.describe(
|
|
50
|
+
'New server-held byte position of the stream after this batch was applied (the tail the next call continues from).',
|
|
51
|
+
),
|
|
52
|
+
capped: z
|
|
53
|
+
.boolean()
|
|
54
|
+
.describe(
|
|
55
|
+
'When true, the per-job log budget is exhausted and further output is dropped.',
|
|
56
|
+
),
|
|
57
|
+
}),
|
|
58
|
+
errors: {
|
|
59
|
+
'lease-stream-mismatch': z.object({}),
|
|
60
|
+
'malformed-log-chunk': z.object({}),
|
|
61
|
+
'append-body-too-large': z.object({maxBytes: z.number().int().positive()}),
|
|
62
|
+
'runner-writer-active': z.object({}),
|
|
63
|
+
'offset-gap': z.object({committedLength: z.number().int().nonnegative()}),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export type LogsModuleClient = InterModuleClient<typeof logsInterModuleContract>;
|
package/src/schemas/append.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {z} from 'zod';
|
|
|
4
4
|
* Append endpoint contract: `POST .../steps/:stepId/logs?attempt=N&offset=B`.
|
|
5
5
|
*
|
|
6
6
|
* The body is raw NDJSON bytes (whole records, newline-terminated), not a
|
|
7
|
-
* Zod-validated object
|
|
7
|
+
* Zod-validated object: it is parsed line by line against the raw log record
|
|
8
8
|
* union (`rawLogRecordSchema`). `offset` is a position in the raw
|
|
9
9
|
* NDJSON spool stream; both `offset` and the returned `committed_length` are
|
|
10
10
|
* bounded far below 2^53 by the accrual budget, so JavaScript `number` is safe.
|
|
@@ -53,3 +53,10 @@ export const offsetGapResponseSchema = z.object({
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
export type OffsetGapResponseDto = z.infer<typeof offsetGapResponseSchema>;
|
|
56
|
+
|
|
57
|
+
/** A lease append was attempted on a stream owned by a server-origin writer. */
|
|
58
|
+
export const logWriterConflictResponseSchema = z.object({
|
|
59
|
+
code: z.literal('log-writer-conflict'),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export type LogWriterConflictResponseDto = z.infer<typeof logWriterConflictResponseSchema>;
|
package/src/schemas/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export {
|
|
|
3
3
|
type AppendLogsResponseDto,
|
|
4
4
|
appendLogsQuerySchema,
|
|
5
5
|
appendLogsResponseSchema,
|
|
6
|
+
type LogWriterConflictResponseDto,
|
|
7
|
+
logWriterConflictResponseSchema,
|
|
6
8
|
type OffsetGapResponseDto,
|
|
7
9
|
offsetGapResponseSchema,
|
|
8
10
|
} from './append.js';
|
|
@@ -21,6 +23,8 @@ export {
|
|
|
21
23
|
parseRawLogRecordLine,
|
|
22
24
|
type RawLogRecord,
|
|
23
25
|
rawLogRecordSchema,
|
|
26
|
+
type ServerLogRecord,
|
|
27
|
+
serverLogRecordSchema,
|
|
24
28
|
} from './record.js';
|
|
25
29
|
export {
|
|
26
30
|
SESSION_VIEW_VERSION,
|
package/src/schemas/read.ts
CHANGED
|
@@ -70,7 +70,9 @@ const readLogsPresignedSchema = z.object({
|
|
|
70
70
|
.number()
|
|
71
71
|
.int()
|
|
72
72
|
.min(0)
|
|
73
|
-
.describe(
|
|
73
|
+
.describe(
|
|
74
|
+
'Committed append byte position for the attempt. This is the active writer CAS axis.',
|
|
75
|
+
),
|
|
74
76
|
truncated: z
|
|
75
77
|
.boolean()
|
|
76
78
|
.describe('True when the stream was force-closed because the runner stopped reporting.'),
|
package/src/schemas/record.ts
CHANGED
|
@@ -2,10 +2,10 @@ import {z} from 'zod';
|
|
|
2
2
|
import {sessionViewRowSchema} from './session-view.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* NDJSON log record contract
|
|
5
|
+
* NDJSON log record contract: one JSON object per line, runner-framed.
|
|
6
6
|
*
|
|
7
7
|
* `offset` / `committed_length` are byte positions in the raw append NDJSON spool stream
|
|
8
|
-
* (envelope included)
|
|
8
|
+
* (envelope included): the offset-CAS axis the runner tracks. The per-job accrual
|
|
9
9
|
* budget charges the normalized NDJSON bytes the server stores, so framing and control records
|
|
10
10
|
* count against it too. The per-record byte caps below bound each record so a single
|
|
11
11
|
* entry's overhead is known and a runner cannot grow storage without moving the
|
|
@@ -123,19 +123,27 @@ export const rawLogRecordSchema = z.discriminatedUnion('type', [
|
|
|
123
123
|
]);
|
|
124
124
|
|
|
125
125
|
/** Stored/read records: regular records, normalized agent sessions, and tombstones. */
|
|
126
|
-
|
|
126
|
+
const storedLogRecordSchemas = [
|
|
127
127
|
logOutput,
|
|
128
128
|
logGroupStart,
|
|
129
129
|
logGroupEnd,
|
|
130
130
|
logEnd,
|
|
131
131
|
logGap,
|
|
132
132
|
agentSession,
|
|
133
|
+
] as const;
|
|
134
|
+
|
|
135
|
+
export const logRecordSchema = z.discriminatedUnion('type', [
|
|
136
|
+
...storedLogRecordSchemas,
|
|
133
137
|
logCapped,
|
|
134
138
|
logRunnerLost,
|
|
135
139
|
]);
|
|
136
140
|
|
|
141
|
+
/** Records a trusted server-origin writer may append: stored records without tombstones. */
|
|
142
|
+
export const serverLogRecordSchema = z.discriminatedUnion('type', storedLogRecordSchemas);
|
|
143
|
+
|
|
137
144
|
export type RawLogRecord = z.infer<typeof rawLogRecordSchema>;
|
|
138
145
|
export type LogRecord = z.infer<typeof logRecordSchema>;
|
|
146
|
+
export type ServerLogRecord = z.infer<typeof serverLogRecordSchema>;
|
|
139
147
|
|
|
140
148
|
export function parseLogRecordLine(line: string): LogRecord {
|
|
141
149
|
return logRecordSchema.parse(JSON.parse(line));
|
|
@@ -144,7 +152,7 @@ export function parseLogRecordLine(line: string): LogRecord {
|
|
|
144
152
|
/**
|
|
145
153
|
* Parses one NDJSON line against the raw write union. A forged
|
|
146
154
|
* server-only `capped` / `runner_lost` record fails here even though it is valid
|
|
147
|
-
* under the read union
|
|
155
|
+
* under the read union: this is the write-path forgery guard.
|
|
148
156
|
*/
|
|
149
157
|
export function parseRawLogRecordLine(line: string): RawLogRecord {
|
|
150
158
|
return rawLogRecordSchema.parse(JSON.parse(line));
|