@eir-labs/coltrane 0.24.34 → 0.24.36

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.
@@ -24,7 +24,7 @@ import { createOutputMirror, defaultMirrorDir, outputPreview, mirrorStorageRef }
24
24
  import { FileLedger, SplitLedger, LedgerError, LEDGER_SCHEMA_VERSION, defaultLedgerPath, defaultGenomeLedgerPath, } from "./ledger.js";
25
25
  import { sealDrill } from "./seal_drill.js";
26
26
  import { standardSimulate } from "./simulate.js";
27
- import { runGig, BudgetExhausted, GigAborted, ResumeRefused, partialGigUsage, partialBudgetState } from "./runtime.js";
27
+ import { runGig, BudgetExhausted, GigAborted, ResumeRefused, partialGigUsage, partialBudgetState, partitionGigInputKeys, unknownGigInputMessage } from "./runtime.js";
28
28
  import { assembleRunDeps, resolveWorkingRepo } from "./run_deps.js";
29
29
  import { createCheckpointStore, createReuseStore } from "./reuse.js";
30
30
  import { killLiveChairChildren } from "./claude_invoker.js";
@@ -3579,6 +3579,81 @@ async function callSurfaceTool(slug, args, deps) {
3579
3579
  // Hosted dispatch NEVER spawns. With a queue seam it queues (the gig table is the
3580
3580
  // queue; a drain worker claims and runs); without one it says so, typed.
3581
3581
  if (deps.queueGig) {
3582
+ // contract-post-time-gig-input-v1 — validate the payload BEFORE it is queued, in the SAME
3583
+ // words as runGig's preflight, so the caller who can still fix the key is the one who is told
3584
+ // (not the worker, later). Everything below the guard queues exactly as today; a throw on this
3585
+ // path would reach the caller as an opaque `tool failed`, so every refusal here is a typed JSON
3586
+ // reply and NEVER an exception.
3587
+ // DOT access on purpose (not a bracket-and-string index): the advertised_args_are_read parser
3588
+ // slurps the last dispatchTool case (venue_credential_mint) to end-of-file, and a bracket read
3589
+ // of a string-literal key here would be miscounted as that tool reading an arg it does not
3590
+ // advertise — this comment must not spell that index pattern out either, or the parser counts
3591
+ // the explanation as an instance of it. gig_dispatch advertises both keys; dot access keeps the
3592
+ // read where it belongs. (Same reason org_hire above reads its args by dot.)
3593
+ const input = args.input;
3594
+ const isPayloadObject = typeof input === "object" && input !== null && !Array.isArray(input);
3595
+ if (isPayloadObject) {
3596
+ const standard_slug = String(args.standard_slug ?? "");
3597
+ const payload = input;
3598
+ // (O4) declared inputs from deps.standards FIRST; only on a miss, exactly one narrow read of
3599
+ // the host-wired reader. The reader may throw or answer undefined — either means "unresolved".
3600
+ let declared;
3601
+ const held = deps.standards?.get(standard_slug);
3602
+ if (held) {
3603
+ declared = held.input_types ?? [];
3604
+ }
3605
+ else if (deps.declaredGigInputs) {
3606
+ try {
3607
+ declared = await deps.declaredGigInputs(standard_slug);
3608
+ }
3609
+ catch {
3610
+ declared = undefined; // a failed read is "could not check", never a guess or a refusal
3611
+ }
3612
+ }
3613
+ if (declared === undefined) {
3614
+ // (F1) neither source could supply the declared inputs. Do NOT guess and do NOT refuse:
3615
+ // queue as today and SAY the payload was not validated, so the reply never implies a check
3616
+ // it could not run. The preflight remains the backstop when the worker runs the gig.
3617
+ try {
3618
+ const data = await deps.queueGig(args);
3619
+ return {
3620
+ ok: true,
3621
+ data,
3622
+ input_validated: false,
3623
+ reason: `the payload was not validated at post time: standard "${standard_slug}" is not on this ` +
3624
+ `surface (deps.standards) and no declaredGigInputs reader resolved its declared inputs. ` +
3625
+ `The gig is queued and runGig's preflight validates it when the worker runs it.`,
3626
+ };
3627
+ }
3628
+ catch (e) {
3629
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
3630
+ }
3631
+ }
3632
+ // The SAME partition the preflight runs: a near miss refuses, other undeclared keys are named.
3633
+ const part = partitionGigInputKeys(Object.keys(payload), declared, (d) => payload[d] !== undefined);
3634
+ if (part.nearMiss !== undefined) {
3635
+ // (O1/O2) a typed refusal in the preflight's exact words — NOTHING is queued, nothing throws.
3636
+ return {
3637
+ ok: false,
3638
+ refusal: "unknown_gig_input",
3639
+ error: unknownGigInputMessage(part.nearMiss.key, part.nearMiss.declaredKey, standard_slug),
3640
+ };
3641
+ }
3642
+ // (O3/I2) no near miss: queue the UNCHANGED args, and name the harmless undeclared keys (sorted)
3643
+ // on the reply so the caller learns at post time what the engine will ignore. A clean payload
3644
+ // adds nothing — `undeclared_input_keys` is present only when there is at least one extra.
3645
+ try {
3646
+ const data = await deps.queueGig(args);
3647
+ return part.extras.length > 0
3648
+ ? { ok: true, data, undeclared_input_keys: part.extras }
3649
+ : { ok: true, data };
3650
+ }
3651
+ catch (e) {
3652
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
3653
+ }
3654
+ }
3655
+ // (F2) an absent or non-object payload is a shape the door already handles — no new refusal,
3656
+ // queued exactly as today.
3582
3657
  try {
3583
3658
  const data = await deps.queueGig(args);
3584
3659
  return { ok: true, data };