@whittlelabs/sifter 0.16.0 → 0.18.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 (3) hide show
  1. package/bin.js +325 -55
  2. package/bin.js.map +4 -4
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -3180,6 +3180,14 @@ var require_device_code = __commonJS({
3180
3180
  });
3181
3181
  log(opts.brand.strings?.pairingDoneMessage ?? `Paired ${displayName}.`);
3182
3182
  }
3183
+ const jobPools = await discoverMemberPools({
3184
+ keepApiUrl: reg.keepApiUrl,
3185
+ jobsApiUrl: reg.jobsApiUrl,
3186
+ clientId: token.clientId,
3187
+ clientSecret: token.clientSecret,
3188
+ fetcher,
3189
+ brand: opts.brand
3190
+ });
3183
3191
  return {
3184
3192
  schemaVersion: 1,
3185
3193
  keepApiUrl: reg.keepApiUrl,
@@ -3195,9 +3203,38 @@ var require_device_code = __commonJS({
3195
3203
  hostname: (0, os_1.hostname)(),
3196
3204
  user: (0, os_1.userInfo)().username
3197
3205
  },
3198
- jobPools: token.jobPools
3206
+ jobPools
3199
3207
  };
3200
3208
  }
3209
+ async function discoverMemberPools(opts) {
3210
+ const tokenUrl = `${opts.keepApiUrl.replace(/\/$/, "")}/api/service-tokens`;
3211
+ const tokenRes = await opts.fetcher(tokenUrl, {
3212
+ method: "POST",
3213
+ headers: { "Content-Type": "application/json" },
3214
+ body: JSON.stringify({
3215
+ clientId: opts.clientId,
3216
+ clientSecret: opts.clientSecret
3217
+ })
3218
+ });
3219
+ (0, version_check_1.checkResponseMinVersion)({
3220
+ response: tokenRes,
3221
+ currentVersion: opts.brand.product.version,
3222
+ packageName: opts.brand.product.packageName
3223
+ });
3224
+ const tokenJson = await readJson(tokenRes);
3225
+ const poolsUrl = `${opts.jobsApiUrl.replace(/\/$/, "")}/api/my/pools`;
3226
+ const poolsRes = await opts.fetcher(poolsUrl, {
3227
+ method: "GET",
3228
+ headers: { Authorization: `Bearer ${tokenJson.data.token}` }
3229
+ });
3230
+ (0, version_check_1.checkResponseMinVersion)({
3231
+ response: poolsRes,
3232
+ currentVersion: opts.brand.product.version,
3233
+ packageName: opts.brand.product.packageName
3234
+ });
3235
+ const poolsJson = await readJson(poolsRes);
3236
+ return poolsJson.data.pools;
3237
+ }
3201
3238
  async function startDeviceCode(opts) {
3202
3239
  const url = `${opts.keepApiUrl.replace(/\/$/, "")}/api/oauth/device`;
3203
3240
  const response = await opts.fetcher(url, {
@@ -15803,6 +15840,17 @@ var require_prompt_execution = __commonJS({
15803
15840
  }
15804
15841
  }
15805
15842
  }
15843
+ if (spec.gates !== void 0) {
15844
+ if (!Array.isArray(spec.gates)) {
15845
+ throw new Error("prompt-execution: spec.gates must be an array when present");
15846
+ }
15847
+ for (const gate of spec.gates) {
15848
+ const g = gate;
15849
+ if (typeof gate !== "object" || gate === null || typeof g.name !== "string" || typeof g.primitive !== "string" || typeof g.params !== "object" || g.params === null) {
15850
+ throw new Error("prompt-execution: each spec.gates entry must be { name, primitive, params }");
15851
+ }
15852
+ }
15853
+ }
15806
15854
  return spec;
15807
15855
  }
15808
15856
  function renderPromptExecution(inputs) {
@@ -16022,6 +16070,8 @@ var require_prompt_execution2 = __commonJS({
16022
16070
  spec.costCapHint = options.costCapHint;
16023
16071
  if (options.progressChannel !== void 0)
16024
16072
  spec.progressChannel = options.progressChannel;
16073
+ if (options.outputGates && options.outputGates.length > 0)
16074
+ spec.gates = options.outputGates;
16025
16075
  if (options.rendering === "producer" && !options.prompt) {
16026
16076
  throw new Error("rendering='producer' requires `prompt`");
16027
16077
  }
@@ -18051,7 +18101,8 @@ var require_canonicalise = __commonJS({
18051
18101
  return {
18052
18102
  name: workflow.name,
18053
18103
  description: workflow.description ?? null,
18054
- ownerOrgId: opts.ownerOrgId,
18104
+ ownerClass: opts.ownerClass,
18105
+ ownerId: opts.ownerId,
18055
18106
  retainPublishedVersions: workflow.retain?.publishedVersions ?? null,
18056
18107
  ...opts.publish !== void 0 ? { publish: opts.publish } : {},
18057
18108
  nodes,
@@ -18081,6 +18132,7 @@ var require_canonicalise = __commonJS({
18081
18132
  promptTemplate: templateText,
18082
18133
  promptTemplatePath: promptOptions.promptTemplate,
18083
18134
  outputSchema: toJsonSchema(promptOptions.outputSchema),
18135
+ ...promptOptions.outputGates ? { outputGates: promptOptions.outputGates } : {},
18084
18136
  outputLabel,
18085
18137
  rendering: promptOptions.rendering,
18086
18138
  ...promptOptions.maxAttempts !== void 0 ? { maxAttempts: promptOptions.maxAttempts } : {},
@@ -18212,18 +18264,28 @@ var require_seed_workflows = __commonJS({
18212
18264
  }
18213
18265
  const results = [];
18214
18266
  for (const workflow of opts.workflows) {
18215
- const ownerOrgId = opts.ownerSlugToOrgId[workflow.owner.slug];
18216
- if (ownerOrgId === void 0) {
18217
- throw new errors_1.SeedWorkflowsError(workflow.name, `seedWorkflows: owner.slug "${workflow.owner.slug}" not present in ownerSlugToOrgId map`);
18267
+ let ownerClass;
18268
+ let ownerId;
18269
+ if ("service" in workflow.owner) {
18270
+ ownerClass = "service";
18271
+ ownerId = workflow.owner.service;
18272
+ } else {
18273
+ const orgId = opts.ownerSlugToOrgId[workflow.owner.orgSlug];
18274
+ if (orgId === void 0) {
18275
+ throw new errors_1.SeedWorkflowsError(workflow.name, `seedWorkflows: owner.orgSlug "${workflow.owner.orgSlug}" not present in ownerSlugToOrgId map`);
18276
+ }
18277
+ ownerClass = "org";
18278
+ ownerId = orgId;
18218
18279
  }
18219
- const opts2 = { ownerOrgId };
18280
+ const opts2 = { ownerClass, ownerId };
18220
18281
  if (opts.env !== void 0)
18221
18282
  opts2.env = opts.env;
18222
18283
  if (opts.publish !== void 0)
18223
18284
  opts2.publish = opts.publish;
18224
18285
  const body = await (0, canonicalise_1.canonicaliseWorkflow)(workflow, opts2);
18225
18286
  logger.info(`seeding workflow ${workflow.name}`, {
18226
- ownerOrgId,
18287
+ ownerClass,
18288
+ ownerId,
18227
18289
  nodes: body.nodes.length,
18228
18290
  transitions: body.transitions.length
18229
18291
  });
@@ -18363,6 +18425,128 @@ var require_chain = __commonJS({
18363
18425
  }
18364
18426
  });
18365
18427
 
18428
+ // ../../packages/shuttle/dist/executors/output-gates.js
18429
+ var require_output_gates = __commonJS({
18430
+ "../../packages/shuttle/dist/executors/output-gates.js"(exports2) {
18431
+ "use strict";
18432
+ Object.defineProperty(exports2, "__esModule", { value: true });
18433
+ exports2.resolvePath = resolvePath;
18434
+ exports2.evaluateOutputGates = evaluateOutputGates;
18435
+ exports2.renderRepairPrompt = renderRepairPrompt;
18436
+ var INPUT_REF = "input:";
18437
+ function resolvePath(root, path) {
18438
+ let ctx = root;
18439
+ for (const seg of path.split(".")) {
18440
+ const project = seg.endsWith("[*]");
18441
+ const key = project ? seg.slice(0, -3) : seg;
18442
+ if (Array.isArray(ctx)) {
18443
+ ctx = ctx.map((el) => key ? el?.[key] : el);
18444
+ } else {
18445
+ ctx = key ? ctx?.[key] : ctx;
18446
+ }
18447
+ if (project && !Array.isArray(ctx)) {
18448
+ ctx = ctx == null ? [] : [ctx];
18449
+ }
18450
+ }
18451
+ return ctx;
18452
+ }
18453
+ function resolveRef(ref, output, inputs) {
18454
+ if (ref.startsWith(INPUT_REF)) {
18455
+ const label = ref.slice(INPUT_REF.length);
18456
+ const row = inputs.find((i) => i.label === label) ?? inputs.find((i) => i.label === "artifacts" && i.payload.label === label);
18457
+ if (!row)
18458
+ return void 0;
18459
+ return Object.prototype.hasOwnProperty.call(row.payload, "value") ? row.payload.value : row.payload;
18460
+ }
18461
+ return resolvePath(output, ref);
18462
+ }
18463
+ function asStrings(value) {
18464
+ if (Array.isArray(value))
18465
+ return value.map((v) => String(v));
18466
+ if (value == null)
18467
+ return [];
18468
+ return [String(value)];
18469
+ }
18470
+ var PRIMITIVES = {
18471
+ /**
18472
+ * `coverage` — the values at `got` must exactly cover the set at `want`:
18473
+ * every wanted value present, nothing invented, none repeated. Params:
18474
+ * `{ got: <output path>, want: <ref>, subject?: <noun> }`.
18475
+ */
18476
+ coverage(params, output, inputs) {
18477
+ const subject = typeof params.subject === "string" ? params.subject : "item";
18478
+ const got = asStrings(resolveRef(String(params.got), output, inputs));
18479
+ const want = asStrings(resolveRef(String(params.want), output, inputs));
18480
+ const gotSet = new Set(got);
18481
+ const wantSet = new Set(want);
18482
+ const missing = want.filter((v) => !gotSet.has(v));
18483
+ const unknown = [...new Set(got.filter((v) => !wantSet.has(v)))];
18484
+ const seen = /* @__PURE__ */ new Set();
18485
+ const duplicated = [];
18486
+ for (const v of got) {
18487
+ if (seen.has(v))
18488
+ duplicated.push(v);
18489
+ else
18490
+ seen.add(v);
18491
+ }
18492
+ if (missing.length === 0 && unknown.length === 0 && duplicated.length === 0)
18493
+ return [];
18494
+ const parts = [];
18495
+ if (missing.length)
18496
+ parts.push(`missing ${subject}(s): [${missing.join(", ")}]`);
18497
+ if (unknown.length)
18498
+ parts.push(`not a known ${subject}: [${unknown.join(", ")}]`);
18499
+ if (duplicated.length)
18500
+ parts.push(`assessed more than once: [${[...new Set(duplicated)].join(", ")}]`);
18501
+ return [
18502
+ `every ${subject} must be covered exactly once \u2014 ${parts.join("; ")}. Return one entry per ${subject}, using its exact id.`
18503
+ ];
18504
+ },
18505
+ /**
18506
+ * `non_empty_when` — the array at `path` must be non-empty, optionally only
18507
+ * when `when.path` equals `when.equals`. Params:
18508
+ * `{ path: <output path>, when?: { path: <output path>, equals: <value> } }`.
18509
+ */
18510
+ non_empty_when(params, output) {
18511
+ const when = params.when;
18512
+ if (when && typeof when.path === "string") {
18513
+ if (resolvePath(output, when.path) !== when.equals)
18514
+ return [];
18515
+ }
18516
+ const path = String(params.path);
18517
+ const value = resolvePath(output, path);
18518
+ if (Array.isArray(value) && value.length > 0)
18519
+ return [];
18520
+ const cond = when && typeof when.path === "string" ? ` when ${when.path} is ${JSON.stringify(when.equals)}` : "";
18521
+ return [
18522
+ `'${path}' must be a non-empty array${cond}; it was ${Array.isArray(value) ? "empty" : "absent"}.`
18523
+ ];
18524
+ }
18525
+ };
18526
+ function evaluateOutputGates(gates, output, inputs) {
18527
+ const findings = [];
18528
+ for (const gate of gates) {
18529
+ const primitive = PRIMITIVES[gate.primitive];
18530
+ if (!primitive) {
18531
+ findings.push({ gate: gate.name, message: `unknown gate primitive '${gate.primitive}'` });
18532
+ continue;
18533
+ }
18534
+ for (const message of primitive(gate.params, output, inputs)) {
18535
+ findings.push({ gate: gate.name, message });
18536
+ }
18537
+ }
18538
+ return { ok: findings.length === 0, findings };
18539
+ }
18540
+ function renderRepairPrompt(findings) {
18541
+ return [
18542
+ "Your previous answer did not satisfy these output checks. Return the corrected, complete answer that resolves every item below. Produce the same output shape with the problems fixed, and nothing else \u2014 no commentary, no explanation of the changes.",
18543
+ "",
18544
+ ...findings.map((f) => `- [${f.gate}] ${f.message}`)
18545
+ ].join("\n");
18546
+ }
18547
+ }
18548
+ });
18549
+
18366
18550
  // ../../packages/keep/dist/client.js
18367
18551
  var require_client2 = __commonJS({
18368
18552
  "../../packages/keep/dist/client.js"(exports2) {
@@ -18375,50 +18559,20 @@ var require_client2 = __commonJS({
18375
18559
  constructor(config) {
18376
18560
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
18377
18561
  this.headers = { "Content-Type": "application/json" };
18378
- if (config.serviceSecret) {
18379
- this.headers["X-Service-Secret"] = config.serviceSecret;
18380
- }
18381
18562
  if (config.serviceToken) {
18382
18563
  this.headers["Authorization"] = `Bearer ${config.serviceToken}`;
18383
18564
  } else if (config.accessToken) {
18384
18565
  this.headers["Authorization"] = `Bearer ${config.accessToken}`;
18385
18566
  }
18386
18567
  }
18387
- // ── Claims ──────────────────────────────────────────────────────
18388
- async checkClaim(subject, scope, resource) {
18389
- return this.request("POST", "/api/claims", {
18390
- subject,
18391
- scope,
18392
- resource
18393
- });
18394
- }
18395
- async checkClaimsBatch(subject, claims) {
18396
- return this.request("POST", "/api/claims/batch", {
18397
- subject,
18398
- claims
18399
- });
18400
- }
18401
- async getEffectivePermissions(subject, context) {
18402
- return this.request("POST", "/api/claims/effective", {
18403
- subject,
18404
- context
18405
- });
18406
- }
18407
- // ── API Key Verification ────────────────────────────────────────
18408
- async verifyApiKey(key) {
18409
- return this.request("POST", "/api/keys/claims", { key });
18410
- }
18411
- // ── Service Tokens ──────────────────────────────────────────────
18412
- async exchangeServiceToken(clientId, clientSecret) {
18413
- return this.request("POST", "/api/service-tokens", {
18414
- clientId,
18415
- clientSecret
18416
- });
18417
- }
18418
- async verifyServiceToken(token) {
18419
- return this.request("POST", "/api/service-tokens/claims", {
18420
- token
18421
- });
18568
+ // ── Actor Tokens ────────────────────────────────────────────────
18569
+ /**
18570
+ * Mint a short-lived `act_` token for an end user, with this client's
18571
+ * service identity recorded as the conduit. Requires this client to be
18572
+ * constructed with a service token holding `keep:actor-tokens:mint`.
18573
+ */
18574
+ async mintActorToken(actor) {
18575
+ return this.request("POST", "/api/actor-tokens", { actor });
18422
18576
  }
18423
18577
  // ── Identity ────────────────────────────────────────────────────
18424
18578
  async createAccount(email, password, name) {
@@ -18510,10 +18664,9 @@ var require_client2 = __commonJS({
18510
18664
  return this.request("POST", `/api/users/${request.ownerPrincipalId}/service-identities`, request);
18511
18665
  }
18512
18666
  /**
18513
- * Bind a role (by name) to a service identity. Pool-scoped bindings
18514
- * pass `scopeRefs: [{kind: 'jobPool', poolId: ...}]`. User-owned
18515
- * pools pass `userId` as the structural principal; team/org-owned
18516
- * pools pass `teamId`/`orgId`.
18667
+ * Bind a role (by name) to a service identity, anchored to a
18668
+ * structural principal: user-owned Sifters pass `userId`;
18669
+ * team/org-anchored bindings pass `teamId`/`orgId`.
18517
18670
  *
18518
18671
  * Requires `keep:role-bindings:write`.
18519
18672
  */
@@ -19533,11 +19686,40 @@ var require_claude_code = __commonJS({
19533
19686
  });
19534
19687
  }
19535
19688
  }
19536
- runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter) {
19689
+ /**
19690
+ * In-session gate repair (ADR 0017): resume the session `execute()` returned a
19691
+ * handle for and correct its output. The repair turn works from the resumed
19692
+ * session's own context (no checkout needed), under the same `--json-schema`
19693
+ * constraint. A throwaway cwd hosts the run; the same handle rides back out so
19694
+ * a second repair round can resume again.
19695
+ */
19696
+ async continue(continuation, followUp, signal, _ctx) {
19697
+ const handle = continuation;
19698
+ if (!handle || typeof handle.sessionId !== "string") {
19699
+ throw new Error("claude-code continue: missing session to resume");
19700
+ }
19701
+ const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
19702
+ const scratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-resume-"));
19703
+ try {
19704
+ const spec = {
19705
+ rendering: "producer",
19706
+ outputLabel: handle.outputLabel,
19707
+ outputSchema: handle.outputSchema
19708
+ };
19709
+ const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId);
19710
+ return response;
19711
+ } finally {
19712
+ await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
19713
+ });
19714
+ }
19715
+ }
19716
+ runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId) {
19537
19717
  const outputLabel = spec.outputLabel;
19538
19718
  const model = modelOverride ?? this.config.model;
19539
19719
  return new Promise((resolve, reject) => {
19540
19720
  const args = ["--print", "--output-format", "stream-json", "--verbose"];
19721
+ if (resumeSessionId)
19722
+ args.push("--resume", resumeSessionId);
19541
19723
  if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
19542
19724
  args.push("--json-schema", JSON.stringify(spec.outputSchema));
19543
19725
  }
@@ -19632,7 +19814,18 @@ var require_claude_code = __commonJS({
19632
19814
  aiProvider: "claude-code",
19633
19815
  ...model ? { aiModel: model } : {},
19634
19816
  ...stream.usage ?? {}
19635
- }
19817
+ },
19818
+ // In-session repair handle (ADR 0017): present only when the run
19819
+ // reported a session id, so the runner can `--resume` it.
19820
+ ...stream.sessionId ? {
19821
+ continuation: {
19822
+ sessionId: stream.sessionId,
19823
+ model,
19824
+ childEnv,
19825
+ outputLabel,
19826
+ outputSchema: spec.outputSchema
19827
+ }
19828
+ } : {}
19636
19829
  },
19637
19830
  readPaths: stream.readPaths,
19638
19831
  toolText: stream.toolText
@@ -19732,6 +19925,7 @@ var require_claude_code = __commonJS({
19732
19925
  let usage = null;
19733
19926
  let resultMeta = null;
19734
19927
  let structuredOutput = null;
19928
+ let sessionId = null;
19735
19929
  let toolText = "";
19736
19930
  for (const line of stdout.split("\n")) {
19737
19931
  const trimmed = line.trim();
@@ -19743,6 +19937,8 @@ var require_claude_code = __commonJS({
19743
19937
  } catch {
19744
19938
  continue;
19745
19939
  }
19940
+ if (typeof event.session_id === "string")
19941
+ sessionId = event.session_id;
19746
19942
  if (event.type === "assistant") {
19747
19943
  const content = event.message?.content;
19748
19944
  if (!Array.isArray(content))
@@ -19779,7 +19975,7 @@ var require_claude_code = __commonJS({
19779
19975
  };
19780
19976
  }
19781
19977
  }
19782
- return { finalText, readPaths, usage, resultMeta, structuredOutput, toolText };
19978
+ return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, toolText };
19783
19979
  }
19784
19980
  var MAX_REPORTED_READS = 2e3;
19785
19981
  function relativeWorkspaceReads(readPaths, cwd) {
@@ -20043,7 +20239,6 @@ var require_anthropic_api = __commonJS({
20043
20239
  }
20044
20240
  async execute(dispatch, signal) {
20045
20241
  const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
20046
- const outputLabel = spec.outputLabel;
20047
20242
  const providerHints = (0, loom_1.pickProviderHints)(spec, this.instance.capabilityId);
20048
20243
  const apiKey = await this.instance.resolveKey(providerHints);
20049
20244
  const hintedModel = providerHints.model;
@@ -20052,6 +20247,33 @@ var require_anthropic_api = __commonJS({
20052
20247
  max_tokens: this.instance.maxTokens,
20053
20248
  messages: [{ role: "user", content: prompt }]
20054
20249
  };
20250
+ return this.callApi(apiKey, body, spec, spec.outputLabel, signal);
20251
+ }
20252
+ /**
20253
+ * In-session gate repair (ADR 0017): re-call the Messages API with the prior
20254
+ * conversation plus the model's own last turn plus the repair turn, under the
20255
+ * same output contract. A fresh conversation is not started — the appended
20256
+ * assistant turn gives the model its previous answer to correct.
20257
+ */
20258
+ async continue(continuation, followUp, signal, _ctx) {
20259
+ const handle = continuation;
20260
+ if (!handle || !Array.isArray(handle.messages)) {
20261
+ throw new Error("anthropic-api continue: missing conversation to resume");
20262
+ }
20263
+ const spec = {
20264
+ rendering: "producer",
20265
+ outputLabel: handle.outputLabel,
20266
+ outputSchema: handle.outputSchema
20267
+ };
20268
+ const body = {
20269
+ model: handle.model,
20270
+ max_tokens: handle.maxTokens,
20271
+ messages: [...handle.messages, { role: "user", content: followUp }]
20272
+ };
20273
+ return this.callApi(handle.apiKey, body, spec, handle.outputLabel, signal);
20274
+ }
20275
+ /** Shared Messages-API call + parse + validate, used by execute and continue. */
20276
+ async callApi(apiKey, body, spec, outputLabel, signal) {
20055
20277
  const timeoutController = new AbortController();
20056
20278
  const timer = setTimeout(() => timeoutController.abort(), this.instance.timeoutMs);
20057
20279
  function onAbort() {
@@ -20099,7 +20321,17 @@ var require_anthropic_api = __commonJS({
20099
20321
  outputs: [{ label: outputLabel, content: parsed ?? { raw: rawText } }],
20100
20322
  rawText,
20101
20323
  parsed,
20102
- ...usage ? { usage } : {}
20324
+ ...usage ? { usage } : {},
20325
+ // In-session repair handle (ADR 0017): the conversation with the model's
20326
+ // answer appended, ready for `continue()` to add the repair turn.
20327
+ continuation: {
20328
+ apiKey,
20329
+ model: body.model,
20330
+ maxTokens: body.max_tokens,
20331
+ messages: [...body.messages, { role: "assistant", content: rawText }],
20332
+ outputLabel,
20333
+ outputSchema: spec.outputSchema
20334
+ }
20103
20335
  };
20104
20336
  } finally {
20105
20337
  clearTimeout(timer);
@@ -20932,6 +21164,7 @@ var require_shuttle = __commonJS({
20932
21164
  var decommission_1 = require_decommission();
20933
21165
  var loom_1 = require_dist3();
20934
21166
  var chain_1 = require_chain();
21167
+ var output_gates_1 = require_output_gates();
20935
21168
  var keep_1 = require_dist4();
20936
21169
  var apply_1 = require_apply();
20937
21170
  var version_check_1 = require_version_check();
@@ -20946,6 +21179,37 @@ var require_shuttle = __commonJS({
20946
21179
  var audit_log_1 = require_audit_log();
20947
21180
  var spend_tracker_1 = require_spend_tracker();
20948
21181
  var store_1 = require_store();
21182
+ var GATE_REPAIR_BUDGET = 2;
21183
+ async function runOutputGates(dispatch, response, executor, signal, ctx) {
21184
+ let spec;
21185
+ try {
21186
+ spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
21187
+ } catch {
21188
+ return { ok: true, response };
21189
+ }
21190
+ const gates = spec.gates;
21191
+ if (!gates || gates.length === 0)
21192
+ return { ok: true, response };
21193
+ let current = response;
21194
+ let result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
21195
+ let attempts = 0;
21196
+ while (!result.ok && attempts < GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
21197
+ try {
21198
+ current = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
21199
+ } catch {
21200
+ break;
21201
+ }
21202
+ result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
21203
+ attempts += 1;
21204
+ }
21205
+ if (result.ok)
21206
+ return { ok: true, response: current };
21207
+ const detail = result.findings.map((f) => `[${f.gate}] ${f.message}`).join(" | ");
21208
+ return {
21209
+ ok: false,
21210
+ reason: `output gate(s) failed${attempts > 0 ? ` after ${attempts} repair attempt(s)` : ""}: ${detail}`
21211
+ };
21212
+ }
20949
21213
  var Shuttle = class {
20950
21214
  brand;
20951
21215
  config;
@@ -21056,6 +21320,12 @@ var require_shuttle = __commonJS({
21056
21320
  await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
21057
21321
  return { status: "failed", reason };
21058
21322
  }
21323
+ const gated = await runOutputGates(dispatch, response, executor, signal, executionCtx);
21324
+ if (!gated.ok) {
21325
+ await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason: gated.reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
21326
+ return { status: "failed", reason: gated.reason };
21327
+ }
21328
+ response = gated.response;
21059
21329
  await observerChain.notify({
21060
21330
  kind: "executor.dispatch.completed",
21061
21331
  jobId,
@@ -21728,7 +21998,7 @@ var import_path = require("path");
21728
21998
  var import_promises = require("fs/promises");
21729
21999
  var import_yaml = __toESM(require_dist());
21730
22000
  var import_shuttle = __toESM(require_dist5());
21731
- var buildVersion = true ? "0.16.0" : pkg.version;
22001
+ var buildVersion = true ? "0.18.0" : pkg.version;
21732
22002
  var sifterBrand = {
21733
22003
  product: {
21734
22004
  id: "sifter",