@sema-agent/sdk 6.17.2 → 7.0.0-rc.1

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/openapi.yaml CHANGED
@@ -480,10 +480,29 @@ paths:
480
480
  parameters:
481
481
  - $ref: '#/components/parameters/PrincipalHeader'
482
482
  - $ref: '#/components/parameters/TaskIdPath'
483
+ # 🔴 #193 车8 P1-11(codex 交叉复审 R1 high,验真后采纳):此前**没声明**这个头,而 server 的 steer 段
484
+ # 真读它并 scoped+sha256 收成 core 的 `inputId`。它是 core 5.14.0 把 pendingSteer 队列化之后唯一能防
485
+ # 「202 丢在路上 → 无键重试 → 同一条操作员指令被再追加一条」的东西(队列只有 3 个位子,三次重试就满,
486
+ # 之后合法转向一律 409 `steering.queue_full`)。最刺眼的自证:本 op 的 409 臂里承诺的
487
+ # `steering.duplicate_input_id` 语义**完全依赖这个头** —— 不声明它,就是声明了一条谁也走不到的臂。
488
+ # ⚠️ 与 submit-class(`/v1/tasks`、`/v1/runs`)不同,这里的键**不是重试许可证**:steer 是
489
+ # AT-MOST-ONCE,SDK 不自动铸键也不因为有键就重试;它纯粹是服务端的去重令牌。
490
+ # ⚠️⚠️ **作用域只到 parked 两腿**(codex R2 复审报出,亲读 server 证实):只有
491
+ # `setPendingSteer(token, scope, steerInput())` 那两条腿把派生的 `inputId` 交给 core;live 腿走的是
492
+ # `live.steer(text, { trusted })`,**没传 inputId**,core 的 `TaskStream.steer()` 今天也不收 ——
493
+ # 所以 200 `applied` 腿**不去重**。这条限定写进下面 409 的描述里,别在别处把它说成整个 verb 的幂等。
494
+ - $ref: '#/components/parameters/IdempotencyKey'
483
495
  post:
484
496
  tags: [runs]
485
497
  operationId: runsSteer
486
- x-status: draft # design/80 D-A; core landing the slice (pendingSteer freeze/close-tag). Skeleton per the pinned wire contract.
498
+ x-status: live # #193 车8 P1-11(2026-08-15):draft 标签过期作废。它当初的理由是「core 正在落
499
+ # pendingSteer freeze/close-tag 这一刀」——那把守卫**早已在 server 侧落地**
500
+ # (`src/http/routes/runs.ts` 的 steer 段:200 applied / 202 queued+parked_for_wake /
501
+ # 409 三码 / 422 steering.invalid_content / Idempotency-Key→core inputId),SDK 自己的
502
+ # `resources/runs.ts` JSDoc 从 [2400] 起就写着「🔴 ONE narrow caveat (NOT "steer is a
503
+ # draft")」。唯一仍是 forward-draft 的是**per-call `mode` 参数**(core 的 steer 今天是
504
+ # harness 级),那条窄注记挂在下面 `mode` 字段自己的 description 上,而不是整个 verb ——
505
+ # 一个字段没接线不构成「整条面未定形」,而 draft 的语义是把它整条排除在契约断言集外。
487
506
  summary: Steer a RUNNING durable run — inject mid-flight direction (supervision).
488
507
  description: >
489
508
  design/80 D-A. The core of *supervision* (vs mere approve/deny): inject direction into a running run.
@@ -538,6 +557,23 @@ paths:
538
557
  content:
539
558
  application/json:
540
559
  schema: { $ref: '#/components/schemas/SteerReceipt' }
560
+ '400':
561
+ # 🔴 #193 车8 P1-11(2026-08-15):**整条 400 臂此前缺席** —— draft 把这条 op 排除在契约断言集外,
562
+ # 于是它攒下了三个从未进 spec 的真码。server 的 steer 段在 run 查找**之前**就跑完这三道校验
563
+ # (`src/http/routes/runs.ts`),所以 400 与运行态无关,live/suspended 上一模一样。
564
+ description: >-
565
+ THREE distinct machine codes share this status (`routes/runs.ts`, all validated BEFORE the run
566
+ lookup — so a 400 is your request's fault regardless of run state):
567
+ errorCode "request.invalid_json" — the body did not parse as JSON;
568
+ errorCode "request.body_shape" — `text` is missing, not a string, or empty (the message is the
569
+ literal accepted shape: `{ text: string (non-empty), mode?: 'all' | 'one-at-a-time',
570
+ priority?: 'now' | 'next' | 'later' }`);
571
+ errorCode "request.field_invalid" — `mode` or `priority` carried an off-enum value. FAIL-LOUD by
572
+ design: the server never coerces an unknown word to a default (a silently-defaulted `priority`
573
+ would look accepted and behave differently).
574
+ content:
575
+ application/json:
576
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
541
577
  '401': { $ref: '#/components/responses/Unauthorized' }
542
578
  '404': { $ref: '#/components/responses/NotFound' }
543
579
  '409':
@@ -554,14 +590,31 @@ paths:
554
590
  sent is already parked with DIFFERENT steering content (same key + same content is an idempotent
555
591
  no-op and 202s). The engine refuses rather than silently picking a side. Reissue with a fresh
556
592
  key — this is a new instruction, not a retry.
593
+ 🔴 SCOPE (#193 车8, codex R2 — read this before treating the key as verb-wide idempotency): this
594
+ code, and idempotency in general, exist ONLY on the PARKED legs (202 `queued` / `parked_for_wake`),
595
+ because only those thread the derived `inputId` into core via `setPendingSteer`. The 200 `applied`
596
+ leg calls core's `TaskStream.steer(text, {trusted})`, which takes NO inputId today — so it does
597
+ NOT dedupe: a lost 200 retried with the SAME key injects the instruction a SECOND time. Worse, the
598
+ receipt's `messageId` is derived from that key, so both receipts are byte-identical and the
599
+ duplicate looks like an idempotent hit. Treat a lost 200 as "outcome unknown", not "safe to retry".
557
600
  content:
558
601
  application/json:
559
602
  schema: { $ref: '#/components/schemas/ErrorResponse' }
560
603
  '413':
604
+ # 🔴 #193 车8 P1-11(2026-08-15)**改判**,依据=亲读 server 两处铸造点。原文写的是
605
+ # `steer.content_too_large` @ 256 KiB —— 那是**另一条腿**的事实:该码只在 subagent steer
606
+ # (`routes/runs.ts` 的子代 verb 区,判 `body.content.length > STEER_IN_MAX_REQUEST_CHARS` = 256 KiB)
607
+ # 与 workflow-agent steer(`routes/workflows.ts`)两处发出。**本条主 run steer 从不发它**:它的
608
+ # 413 来自 `readJson` 抛的 `HttpError(413, "request body too large")`(**不带 code**),经
609
+ # `httpErrorCode(413, undefined)`(`src/http/send.ts`)落成 `request.payload_too_large`,阈值是
610
+ # `MAX_BODY` = 8 MiB。按旧文案写 `errorCode === "steer.content_too_large"` 的消费方在这条腿上永不命中。
561
611
  description: >-
562
- errorCode "steer.content_too_large" — the steer body exceeded the server's request cap (256 KiB,
563
- server 1.279.2). REJECTED, never truncated: server-side truncation would make the engine's
564
- "[+N chars]" disclosure under-report. Shorten and resend; retrying the same bytes is pointless.
612
+ errorCode "request.payload_too_large" — the request body exceeded the worker's global body cap
613
+ (`MAX_BODY`, 8 MiB), raised by the shared JSON reader before this handler sees a `text` at all.
614
+ REJECTED, never truncated. Shorten and resend; retrying the same bytes is pointless.
615
+ ⚠️ NOT `steer.content_too_large` — that code (and its 256 KiB threshold) belongs to the SUBAGENT
616
+ steer `POST /v1/runs/{taskId}/subagents/{target}/steer` and the workflow-agent steer, which cap
617
+ the steer TEXT itself. This verb has no separate text cap.
565
618
  content:
566
619
  application/json:
567
620
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -571,6 +624,21 @@ paths:
571
624
  application/json:
572
625
  schema: { $ref: '#/components/schemas/ErrorResponse' }
573
626
  '429': { $ref: '#/components/responses/RateLimited' } # (declared 2026-07-31) rateLimited||quotaExceeded||leaseDenied all gate this verb (routes/runs.ts)
627
+ '501':
628
+ # 🔴 #193 车8 P1-11(2026-08-15):**臂缺席,而这条 op 自己的 description 早就在说 501**
629
+ #(「A worker with no checkpoint store answers 501 instead」)—— 散文说有、机器面没有,正是
630
+ # 生成式消费方结构性看不见的那一格。两个码来自两处不同的谓词,处置也不同(见描述)。
631
+ description: >-
632
+ TWO distinct capability codes share this status (`routes/runs.ts`):
633
+ errorCode "capability.run_store_required" — the worker wires no durable run store
634
+ (DB_BACKEND=mysql|pg), so the whole async-run family is off. Nothing about THIS request can fix it;
635
+ read the `capabilities` bit instead of probing.
636
+ errorCode "capability.checkpoint_store_required" — the run store IS wired and the run is durably
637
+ PARKED, but there is no checkpoint store to park the steer on. The live-run path (200 `applied`)
638
+ still works on such a worker — only the parked leg 501s.
639
+ content:
640
+ application/json:
641
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
574
642
 
575
643
  /v1/runs/{taskId}/subagents/{target}/steer:
576
644
  parameters:
@@ -2619,10 +2687,29 @@ paths:
2619
2687
  get:
2620
2688
  tags: [trace]
2621
2689
  operationId: traceStream
2622
- x-status: draft # trace relay (same source/semantics as runs.events); kept for the workspace live view.
2690
+ x-status: live # #193 车8 P2-15②(2026-08-15):trace relay,2026-06 起成熟在产(cli `-p` 腿消费,
2691
+ # live SSE 的 `id:` 游标行早已在发)。draft 标签过期作废——它的语义是「响应形还没定
2692
+ # 形、别过度断言」,而这条流的形自 `TraceStreamEvent` 那次修正(2026-07-25)起就已按
2693
+ # server 真码钉死(`routes/trace-usage.ts` 的 `streamTaskTrace` + `trace/project.ts`
2694
+ # 的 `mapTraceEvent`)。属主裁定翻 live,一并补齐 draft 免检期里漏掉的 416 臂与
2695
+ # `from` 续传参数(两者都真在 server 上,只是从没进机器可读面)。
2623
2696
  summary: Trace relay SSE (same source/semantics as runs.events).
2624
2697
  parameters:
2625
2698
  - $ref: '#/components/parameters/LastEventId'
2699
+ # 🔴 #193 车8 P2-15②(2026-08-15):server 的 resume 点是
2700
+ # `req.headers["last-event-id"] ?? searchParams.get("from")`(`routes/trace-usage.ts` 的
2701
+ # `streamTaskTrace`)—— **两个入口同源**,spec 此前只声明了 header 那一个。后果不是文档不全:一个
2702
+ # 不能自定义 SSE 请求头的消费方(浏览器原生 `EventSource` 就是这一类)照 spec 生成客户端时,
2703
+ # 结构上拿不到任何可用的续传入口,只能每次从头重放整条流。
2704
+ - name: from
2705
+ in: query
2706
+ required: false
2707
+ schema: { type: integer }
2708
+ description: >
2709
+ Resume cursor — the durable `task_event.seq` to replay AFTER (the same value an `id:` line
2710
+ carries). Equivalent to `Last-Event-ID`, which WINS when both are sent. For callers that cannot
2711
+ set request headers (native `EventSource`) this is the only resume entry. A non-numeric or
2712
+ negative value replays from the beginning (no error).
2626
2713
  responses:
2627
2714
  '200':
2628
2715
  # 🔴 修正(2026-07-25):此处原先 $ref 到 `AgentEvent`,是真错 —— 而且本文件自己就矛盾:专门为这条路写的
@@ -2641,6 +2728,20 @@ paths:
2641
2728
  schema: { $ref: '#/components/schemas/TraceStreamEvent' }
2642
2729
  '401': { $ref: '#/components/responses/Unauthorized' }
2643
2730
  '404': { $ref: '#/components/responses/NotFound' }
2731
+ '416':
2732
+ # 🔴 #193 车8 P2-15②(2026-08-15):**整条 416 臂此前缺席**,而这是 resumable 流的**正常**降级路径:
2733
+ # `streamTaskTrace` 在写 SSE 头**之前**判 `retainedFrom > from + 1` ⇒
2734
+ # `sendError(res, 416, "limit.retention_evicted", …, { retainedFrom })`。三处早就知道它
2735
+ #(server 真码 / `TraceStreamEvent` 的 description / SDK 的 `trace.stream()` 实现),唯独机器可读的
2736
+ # responses 里没有。姊妹面 `GET /v1/runs/{taskId}/events` 是正确形对照组,复用它的同一具名信封。
2737
+ description: >-
2738
+ errorCode "limit.retention_evicted" — the resume point (`Last-Event-ID` / `from`) was evicted past
2739
+ retention, so the gap cannot be replayed. Sent BEFORE any SSE header, so this is an ordinary JSON
2740
+ error response, not a stream frame. Read `retainedFrom`, full-sync the gap via
2741
+ `GET /v1/tasks/{taskId}/turns`, then resume the stream from `retainedFrom`.
2742
+ content:
2743
+ application/json:
2744
+ schema: { $ref: '#/components/schemas/ResumeEvicted' }
2644
2745
  '501': { $ref: '#/components/responses/NotImplemented' } # (declared 2026-07-31) `routes/trace-usage.ts`'s `!deps.runStore` guard fires before the /turns|/stream|/artifacts sub-dispatch — capability.run_store_required
2645
2746
 
2646
2747
  /v1/usage:
@@ -3392,7 +3493,9 @@ paths:
3392
3493
  operationId: imagesRegister
3393
3494
  x-status: live # operator face; entry shape = the image-index row (open, server-validated).
3394
3495
  summary: 'OPERATOR: register one image-index entry → { id }.'
3395
- requestBody: { required: true, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
3496
+ # 🔴 #193 车8 P1-12(2026-08-15):此前是裸开集 object(= 生成式消费方拿到 `any`),现挂具名
3497
+ # `ImageRegisterRequest`(字段集对 `routes/images.ts` 亲读,含每个键的 server 侧默认值)。
3498
+ requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ImageRegisterRequest' } } } }
3396
3499
  responses:
3397
3500
  '200': { description: '{ id }.', content: { application/json: { schema: { type: object, required: [id], properties: { id: { type: string } }, additionalProperties: false } } } }
3398
3501
  '400': { $ref: '#/components/responses/BadRequest' }
@@ -3446,7 +3549,10 @@ paths:
3446
3549
  description: >
3447
3550
  Whitelisted-args validation (400/501); an identical-argv in-flight bake is idempotently reused (202);
3448
3551
  busy ⇒ 409 {activeBakeId?, eventsUrl?}; submission rate-limit ⇒ 429 + retryAfterSec.
3449
- requestBody: { required: true, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
3552
+ # 🔴 #193 车8 P1-12(2026-08-15):挂具名 `BakeCreateRequest`(字段集对 `images/bake-validate.ts` 的
3553
+ # `validateBake` 亲读)。一条 build-host-RCE-capable 的门用裸开集 object 描述请求体,与它自己的
3554
+ # 「每个字段都过冻结闭集」纪律直接矛盾。
3555
+ requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/BakeCreateRequest' } } } }
3450
3556
  responses:
3451
3557
  '202': { description: '{ bakeId, eventsUrl, status, state }.', content: { application/json: { schema: { $ref: '#/components/schemas/BakeSubmitAck' } } } }
3452
3558
  '400': { $ref: '#/components/responses/BadRequest' }
@@ -3558,7 +3664,9 @@ paths:
3558
3664
  x-status: live
3559
3665
  summary: 'RUNNER: push one build.sh event line + lease heartbeat (x-bake-ingest-secret header from the claim).'
3560
3666
  description: 'line = {event, …}; event=heartbeat renews the lease only. 409 = lease lost/stolen; cancelRequested=true ⇒ the runner should kill the build.'
3561
- requestBody: { required: true, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
3667
+ # 🔴 #193 车8 P1-12(2026-08-15):挂具名 `BakeIngestLine`(字段集对 `routes/images.ts` 的
3668
+ # `ingestBakeLine` 亲读)。刻意保持**开集** —— 非终态帧的其余键是 build.sh 帧的逐字透传。
3669
+ requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/BakeIngestLine' } } } }
3562
3670
  responses:
3563
3671
  '200': { description: 'One of four shapes (heartbeat / non-terminal append / terminal done-success / terminal done-needs-flip) — see BakeIngestAck.', content: { application/json: { schema: { $ref: '#/components/schemas/BakeIngestAck' } } } }
3564
3672
  '403':
@@ -5325,11 +5433,15 @@ components:
5325
5433
  ResumeEvicted:
5326
5434
  type: object
5327
5435
  description: >
5328
- 416 body for an evicted SSE resume point (`GET /v1/runs/:id/events`). Carries the machine code
5329
- `errorCode: "limit.retention_evicted"`; matching the 416 STATUS alone is equally valid. Read
5330
- `retainedFrom`, then full-sync via `/v1/tasks/:id/turns` (pinned SSE contract).
5436
+ 416 body for an evicted SSE resume point. Shared by BOTH resumable streams — `GET /v1/runs/:id/events`
5437
+ (AgentEvent vocabulary) and `GET /v1/tasks/{taskId}/stream` (the block-grained trace vocabulary): the
5438
+ two handlers mint it through the SAME `sendError(…, 416, "limit.retention_evicted", …, {retainedFrom})`
5439
+ site shape, so one envelope serves both. Carries the machine code `errorCode:
5440
+ "limit.retention_evicted"`; matching the 416 STATUS alone is equally valid. Read `retainedFrom`, then
5441
+ full-sync via `/v1/tasks/:id/turns` (pinned SSE contract).
5331
5442
  (Corrected 2026-07-31 — see the 416 response note on that path: the "no machine code" claim predates
5332
- the site's move onto the shared `sendError`.)
5443
+ the site's move onto the shared `sendError`. Widened to both paths by #193 车8 P2-15②, 2026-08-15 —
5444
+ the trace stream had the same 416 arm in code all along, just never declared.)
5333
5445
  required: [error, errorCode, retainedFrom]
5334
5446
  properties:
5335
5447
  error: { type: string }
@@ -5432,6 +5544,52 @@ components:
5432
5544
  capabilities: { $ref: '#/components/schemas/ImageCapabilities' }
5433
5545
  manifestSha: { type: ['string', 'null'], description: 'the source entry''s manifestSha — nullable (ImageIndexEntry.manifestSha is string|null; was wrongly non-nullable here).' }
5434
5546
 
5547
+ ImageRegisterRequest:
5548
+ # 🔴 新(#193 车8 P1-12,2026-08-15)—— `POST /v1/images/register` 的请求体。此前这条 op 的 requestBody
5549
+ # 是裸 `{type: object, additionalProperties: true}`(SDK 侧对应 `Record<string, unknown>`):operator
5550
+ # 面上一个**打错的键名**既不会被 server 拒(白名单是「读已知键」,不是「拒未知键」),也不会被类型
5551
+ # 或校验器拦下 —— 它安静地落成该字段的默认值。字段集逐条对 `src/http/routes/images.ts` 的 register
5552
+ # 分支亲读:三个必填由 handler 显式 400(`request.field_invalid`),其余每一个都有 handler 里写死的默认值。
5553
+ #
5554
+ # ⚠️ `additionalProperties: false` 在这里是**客户端护栏**,不是 server 行为的镜像:server 并不拒收
5555
+ # 未知键,它只是**读不到就用默认值**。封闭声明的价值恰在于把那个静默 no-op 变成一个当场可见的错误。
5556
+ # ⚠️ `id` / `createdAt` / `updatedAt` 刻意不在场:handler 一个都不读(id 由 `idFor(repo,digest)` 确定性
5557
+ # 派生,两个时间戳由 store 写)。送它们是纯粹的误解,所以封闭形要让它们编译不过。
5558
+ type: object
5559
+ description: >
5560
+ `POST /v1/images/register` body — the operator-only image-index upsert (P1 seeding; the P3
5561
+ atomic-publish pipeline drives it for real). STATUS-MONOTONIC: `registerBuilding` never downgrades an
5562
+ already-`published` row, so this door cannot de-publish a live image (take-down is the explicit
5563
+ deprecate path). Everything except profile/repo/digest is optional with a server-side default.
5564
+ required: [profile, repo, digest]
5565
+ additionalProperties: false
5566
+ properties:
5567
+ profile: { type: string, description: 'REQUIRED — missing/non-string ⇒ 400 request.field_invalid.' }
5568
+ repo: { type: string, description: 'REQUIRED — same 400.' }
5569
+ digest: { type: string, description: 'REQUIRED — same 400. The immutable "sha256:<64hex>" this entry is keyed on.' }
5570
+ # ⚠️ 下面每一条 enum 都是**契约**,不是 server 的运行期校验:除 profile/repo/digest 三个必填外,
5571
+ # handler 对取值**不做集合校验** —— 一个打错的 `status: "publish"` 会原样落库,而
5572
+ # `latestPublished` 只认 `"published"` ⇒ 这张镜像从此对选择面隐形,且没有任何错误。
5573
+ # 这正是把这条体从裸开集 object 收成具名封闭形的理由。
5574
+ bands: { type: array, items: { type: string }, description: 'Absent / not-an-array ⇒ `[]`.' }
5575
+ tag: { type: string, description: 'Absent / non-string ⇒ `""`.' }
5576
+ toolchainVersions: { type: object, additionalProperties: { type: string }, description: 'Absent ⇒ `{}`.' }
5577
+ capabilities: { $ref: '#/components/schemas/ImageCapabilities' }
5578
+ podContract: { $ref: '#/components/schemas/ImagePodContract' }
5579
+ sizeBytes: { type: ['integer', 'null'], description: 'Anything that is not a number ⇒ `null` (no coercion).' }
5580
+ status: { type: string, enum: [building, published, deprecated, failed], description: 'Absent / non-string ⇒ `"building"`.' }
5581
+ visibility:
5582
+ type: string
5583
+ enum: [public, tenant]
5584
+ description: 'STRICT equality to `"tenant"` selects tenant; EVERY other value (including a typo) ⇒ `"public"`.'
5585
+ tenantId: { type: ['string', 'null'], description: 'Non-string ⇒ `null`.' }
5586
+ manifestSha: { type: ['string', 'null'], description: 'Non-string ⇒ `null`.' }
5587
+ recipeGitSha: { type: ['string', 'null'], description: 'Non-string ⇒ `null`.' }
5588
+ generatorVersion: { type: ['string', 'null'], description: 'Non-string ⇒ `null`.' }
5589
+ buildDate: { type: ['string', 'null'], description: 'ISO date-time string; non-string ⇒ `null`.' }
5590
+ supersedes: { type: ['string', 'null'], description: 'Version-lineage anchor; non-string ⇒ `null`.' }
5591
+ signed: { type: boolean, description: 'STRICT `=== true`; anything else (including the string "true") ⇒ `false`.' }
5592
+
5435
5593
  QuotaError:
5436
5594
  type: object
5437
5595
  description: >
@@ -6211,6 +6369,7 @@ components:
6211
6369
  type: array
6212
6370
  items: { type: string }
6213
6371
  description: 'E7 (SHIPPED) — the /effort picker default set (minimal/low/medium/high); present ONLY for a reasoning model.'
6372
+ atMentionable: { type: boolean, description: '#233/A-002.8 (SHIPPED) — @model mention allowlist verdict; present ONLY when the deployment configured an at-mention allowlist. Absent = no allowlist verdict, NOT "not mentionable" — do not narrow absence to false.' }
6214
6373
 
6215
6374
  ElicitResponse:
6216
6375
  type: object
@@ -10008,6 +10167,153 @@ components:
10008
10167
  type: [string, 'null']
10009
10168
  enum: [band-conflict, duplicate, disk, timeout, cancelled, null]
10010
10169
 
10170
+ BakeCreateRequest:
10171
+ # 🔴 新(#193 车8 P1-12,2026-08-15)—— `POST /v1/images/bakes` 的请求体。此前是裸
10172
+ # `{type: object, additionalProperties: true}`。这条门是 §P2.4 的**反 RCE 核心**:bands/profile/
10173
+ # baseRef/bump 组合出一个 Dockerfile + 一个 FROM + 一次 docker build = build host 上的任意代码,所以
10174
+ # 每个字段在**任何 argv 被拼出来之前**就按冻结闭集校验(`src/images/bake-validate.ts` 的 `validateBake`)。
10175
+ # 一个连字段名都不具名的请求体和这条纪律是矛盾的:调用方连「哪些键会被看」都无从知道。
10176
+ # 字段集逐条对 `validateBake` 的 `BakeSubmit` 亲读,外加 `routes/images.ts` 自己读的 `idempotencyKey`。
10177
+ #
10178
+ # ⚠️ profile 与 bands 是 **XOR**(恰好给一个):两个都给 ⇒ 400「provide profile OR bands, not both」,
10179
+ # 一个都不给 ⇒ 400「one of profile | bands is required」。JSON Schema 表达不出「恰好一个」而不牺牲
10180
+ # 可读性,所以两者都标 optional 并在这里说清 —— 判据的属主是 server 的校验器,不是本文件。
10181
+ type: object
10182
+ description: >
10183
+ `POST /v1/images/bakes` body — the operator-only bake submit. Give EXACTLY ONE of `profile` | `bands`.
10184
+ Every value is checked against a FROZEN closed set server-side before any argv exists; an off-list
10185
+ value is a 400 (or a 501 for the reserved `custom` band = the P4 tenant affordance), NEVER coerced.
10186
+ The output repo/tag and the `FROM` base are server-fixed — a caller cannot set them.
10187
+ ⚠️ `additionalProperties: false` here is a CLIENT-SIDE guardrail, not a mirror of server behaviour:
10188
+ `validateBake` reads NAMED fields, so an unknown key is silently ignored rather than rejected — a
10189
+ typo'd knob is a wire no-op. Declaring the body closed is what turns that no-op into a visible error.
10190
+ additionalProperties: false
10191
+ properties:
10192
+ profile:
10193
+ type: string
10194
+ description: >
10195
+ One of the FROZEN profile catalog (`code-python` … `code-full-db`, mirrored from the recipe
10196
+ `gen-dockerfile.sh`). Unknown profile ⇒ 400. XOR with `bands`.
10197
+ bands:
10198
+ type: array
10199
+ items: { type: string }
10200
+ description: >
10201
+ Non-empty, duplicate-free list from the FROZEN band set (one dir per band under the recipe's
10202
+ `bands/`). XOR with `profile`; a `bands` bake records profile="custom". An off-list id ⇒ 400
10203
+ (with `conflictBands` for the composer UI); the reserved id `custom` ⇒ 501 "custom/tenant bake is P4".
10204
+ baseRef:
10205
+ type: string
10206
+ description: >
10207
+ An immutable `sha256:<64hex>` — and it must additionally be a KNOWN PUBLISHED `sandbox-base`
10208
+ digest the index has seen (400 `request.unknown_reference` otherwise). Absent ⇒ the server picks
10209
+ the base itself (`SANDBOX_BASE_REF` pin, else the index's latest published sandbox-base), which is
10210
+ why a caller can never inject an arbitrary FROM.
10211
+ bump:
10212
+ type: object
10213
+ additionalProperties: { type: string }
10214
+ description: >
10215
+ `{ tool: version }` — tool ∈ the FROZEN bump-tool set, version ∈ `^[A-Za-z0-9._-]{1,40}$`.
10216
+ Off-list tool or off-range version ⇒ 400.
10217
+ push: { type: boolean, description: 'STRICT `=== true`; anything else ⇒ false (node-local build, no registry push).' }
10218
+ dryRun: { type: boolean, description: 'STRICT `=== true`. Resolve-only: bypasses the single-flight busy guard and never takes the lease.' }
10219
+ logs: { type: boolean, description: 'STRICT `=== true`. Opt-in raw build-log tail rows on the event stream.' }
10220
+ pkgSource:
10221
+ type: string
10222
+ enum: [cn, global]
10223
+ description: 'Bake-time package-source profile (RFC B4). Closed set, never coerced; absent ⇒ build.sh default (cn).'
10224
+ idempotencyKey:
10225
+ type: string
10226
+ description: >
10227
+ Durable submit-idempotency, read ONLY when the `Idempotency-Key` HEADER is absent (the header
10228
+ wins). Scoped to the submitting principal server-side, so a replay dedupes for the SAME caller only.
10229
+
10230
+ BakeIngestLine:
10231
+ # 🔴 新(#193 车8 P1-12,2026-08-15)—— `POST /v1/images/bakes/{bakeId}/ingest` 的请求体(一行 build.sh
10232
+ # 事件)。此前是裸 `{type: object, additionalProperties: true}`;SDK 侧同样是 `Record<string, unknown>`,
10233
+ # 于是 runner 侧写错一个键名(`manifestSha` 打成 `manifest_sha`)在两侧都是静默的。
10234
+ # 字段集对 `routes/images.ts` 的 `ingestBakeLine` + ingest handler 亲读:handler 只在 `event` 上分派,
10235
+ # 终态 `done` 那一支另读 8 个键,非终态支把**剩余全部键**原样(经 redactDeep)存进事件 data。
10236
+ #
10237
+ # ⚠️ 这条**刻意保持开集**(`additionalProperties: true`,与上面两条请求体相反):非终态帧的 `...data`
10238
+ # 是 build.sh 帧字段的逐字透传,封闭它等于在这里发明一份 build.sh 没有承诺过的帧契约。
10239
+ type: object
10240
+ description: >
10241
+ One runner-forwarded build.sh event line. The handler dispatches on `event` alone; a
10242
+ `heartbeat` renews the lease and appends NO event, every other value appends one durable event whose
10243
+ data is the line MINUS `event`/`lineOrd`/`line_ord` (redacted). OPEN by design — build.sh frame fields
10244
+ ride through verbatim.
10245
+ required: [event]
10246
+ additionalProperties: true
10247
+ properties:
10248
+ event:
10249
+ type: string
10250
+ description: >
10251
+ build.sh's frame name — `resolved | state | step | image | manifest | log | done`, plus the
10252
+ runner-synthesized `heartbeat`. Open set: an unknown name is stored under that name as the SSE
10253
+ kind. A non-string ⇒ treated as `""` (still a non-terminal append), never an error.
10254
+ lineOrd: { type: integer, description: 'Per-line ordinal — the at-least-once dedupe key (uq_line). `line_ord` is accepted as a snake_case alias.' }
10255
+ line_ord: { type: integer, description: 'snake_case alias of `lineOrd` (the handler reads either).' }
10256
+ state:
10257
+ # 🔴 codex R2 复审后统一口径:**开集**,与 `BakeRecordView.state` 的闭集刻意不对称。请求侧镜像
10258
+ # **产者**的 wire —— server 自己的 `bake-runner/protocol.ts` 把它声明为 `state: string`,且
10259
+ # `setState` 落库只做 `as BakeState` 强转、不做集合校验;响应侧才镜像闭集域类型。
10260
+ type: string
10261
+ description: >-
10262
+ `event:"state"` only — denormalizes the UI progress bar. NEVER the coarse terminality (§P2.3).
10263
+ Today's values: PENDING | BUILDING | PUSHING | VERIFYING | REGISTERING | COMPLETE | FAILED |
10264
+ CANCELLED. OPEN on purpose: build.sh owns this vocabulary and the server stores it unvalidated,
10265
+ so a future ninth state must not make a legal frame fail validation.
10266
+ level: { type: string, description: '`event:"log"` only — the literal `"raw"` marks an opt-in build-log tail row (evicted first).' }
10267
+ status:
10268
+ # 🔴 自审修(#193 车8,提交后本车自查):这个键**跨帧种复用**,所以它绝不能是闭集 enum。
10269
+ # `done` 帧上是 COMPLETE|FAILED|CANCELLED(handler 唯一读它的地方);而 **`step` 帧上同名键**
10270
+ # 带的是 build.sh 的步骤状态(runner 的 `dockerBuildTickFrame` 就恒发 `status:"running"`,
10271
+ # server 自铸的 register step 发 running|ok|fail|skipped)。写死三值 enum ⇒ 每一条合法的
10272
+ # step 帧都被严格校验器判违约 —— 与本文件里 `Event_tool_end.errorCode` 那条「开集写 enum 比
10273
+ # 漏声明更糟」的裁定同族,方向一致。
10274
+ type: string
10275
+ description: >
10276
+ OVERLOADED across frame kinds — do NOT treat as a closed set. On `event:"done"` (the only place the
10277
+ handler reads it) it is the build outcome `COMPLETE` | `FAILED` | `CANCELLED`, and a non-string is
10278
+ treated as FAILED. On `event:"step"` frames the SAME key carries build.sh's step status
10279
+ (`running` / `ok` / `fail`), which the handler does not read — it rides through into the event data.
10280
+ exitCode:
10281
+ type: [integer, 'null']
10282
+ description: >-
10283
+ `event:"done"` only. Absent ⇒ 0 on COMPLETE, else null. 🔴 EXPLICITLY nullable: the runner's
10284
+ cancel arm (`bake-runner/protocol.ts`) sends `{status:"CANCELLED", exitCode: null}` verbatim.
10285
+ error:
10286
+ type: [string, 'null']
10287
+ description: >-
10288
+ `event:"done"` only — free text; secret-redacted server-side before storage. 🔴 EXPLICITLY
10289
+ nullable: the runner's push-recovery leg (`bake-runner/runner.ts`, exitCode===13) sends
10290
+ `{status:"COMPLETE", exitCode: 0, error: null}` verbatim.
10291
+ tag: { type: string, description: '`event:"done"` only; absent ⇒ the bake row''s existing tag.' }
10292
+ digest: { type: string, description: '`event:"done"` only — the built image digest (a no-push build sends build.sh''s node-local LOCAL_ID).' }
10293
+ manifestBody: { type: object, additionalProperties: true, description: '`event:"done"` only — the build manifest BODY the runner read off the build host (`manifest`/`done` carry only a host FS path). Required for auto-register.' }
10294
+ manifest:
10295
+ # 🔴 codex R2 复审报出、验真属实:build.sh 的 done 行带的是 manifest **路径字符串**
10296
+ # (`bake-runner/protocol.ts` 的 `manifestPathFromLine` 首条分支就是 typeof === "string"),
10297
+ # runner 逐字转发。声明成纯 object ⇒ 严格校验器拒收每一条真实 terminal ingest。
10298
+ oneOf:
10299
+ - { type: string }
10300
+ - { type: object, additionalProperties: true }
10301
+ description: >-
10302
+ EITHER build.sh's manifest PATH (a build-host filesystem string, what the real `done` line
10303
+ carries) OR the manifest body object. When it is an object it is the fallback source for the
10304
+ manifest body and its `sha256` is the fallback for `manifestSha`; when it is a string those two
10305
+ fallbacks simply do not fire (the server gates on `typeof manifestBody === "object"`).
10306
+ manifestSha: { type: string, description: '`event:"done"` only; falls back to `manifest.sha256`, then the bake row''s stored value.' }
10307
+ errorCode:
10308
+ type: string
10309
+ enum: [band-conflict, duplicate, disk, timeout, cancelled]
10310
+ description: >
10311
+ `event:"done"` only — a runner-supplied structured terminal code, PREFERRED over the exit-code
10312
+ recompute (the runner knows codes the exit code cannot reconstruct, e.g. the disk guard). Validated
10313
+ by set membership: an off-list value is DROPPED (never trusted as free text), not an error.
10314
+ conflictBands: { type: array, items: { type: string }, description: '`event:"done"` only — honored ONLY when the FINAL code is `band-conflict`; the composer UI red-highlights these chips.' }
10315
+ sizeBytes: { type: integer, description: '`event:"done"` only — rides the terminal done event; non-number ⇒ null.' }
10316
+
10011
10317
  BakeClaim:
10012
10318
  type: object
10013
10319
  description: >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "6.17.2",
3
+ "version": "7.0.0-rc.1",
4
4
  "description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP façade + web). Server-side only — tokens never enter the browser.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",