@miraland-labs/conduit-bridge 0.11.1 → 0.11.3

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Local Bridge CLI for [Conduit](https://github.com/miralandlabs/conduit). Connects a computer to one organization, claims work, and drives a local agent.
4
4
 
5
- **Package version:** `0.11.0` — **ExecutionClass inversion:** the control plane stamps `execution_class` on the assignment; Bridge launches from that stamp (fail closed if missing), asserts the full class floor after ceilings, and projects permissions locally (“Bridge wins” on tools). Ordinary grants are derived server-side as classMax ∩ project ceiling; landing is package `lands`, not a class axiom. Workspace briefs discover verification commands for Node, Make, Cargo, Go, pytest, and Gradle. Control-plane `instruction` is **additive** evidence/class prose; Bridge always appends local RULES (shell allow-list, verbatim paste, criterion mapping, hard-denied). Prompt is data; permissions stay local — residual risk if the control plane is compromised (sharpest for `observe_network`). Artifact digests still normalize bare 64-hex to `sha256:<hex>`. Experimental Pi lane remains; `observe_network` is refused on Pi. Heartbeat protocol 2, worktrees, ops helpers, multi-driver lanes, and slots **1–4** unchanged. Protocol 1 heartbeats remain compatible for presence but cannot receive work.
5
+ **Package version:** `0.11.3` — a lost connection to the code host during delivery finalize is now reported as **retryable**: that block marked every throw non-retryable, which is right for a malformed delivery report and wrong for a dropped connection, and it reported a GitHub HTTP/2 flake on `git push` as the agent failing its execution contract. Since `0.11.0`: `0.11.1` made a missing stamped class retryable-then-fail-closed, `0.11.2` removed the unread `external_network` spec field and made a driver refusing a class it cannot enforce terminal. **ExecutionClass inversion:** the control plane stamps `execution_class` on the assignment; Bridge launches from that stamp (fail closed if missing), asserts the full class floor after ceilings, and projects permissions locally (“Bridge wins” on tools). Ordinary grants are derived server-side as classMax ∩ project ceiling; landing is package `lands`, not a class axiom. Workspace briefs discover verification commands for Node, Make, Cargo, Go, pytest, and Gradle. Control-plane `instruction` is **additive** evidence/class prose; Bridge always appends local RULES (shell allow-list, verbatim paste, criterion mapping, hard-denied). Prompt is data; permissions stay local — residual risk if the control plane is compromised (sharpest for `observe_network`). Artifact digests still normalize bare 64-hex to `sha256:<hex>`. Experimental Pi lane remains; `observe_network` is refused on Pi. Heartbeat protocol 2, worktrees, ops helpers, multi-driver lanes, and slots **1–4** unchanged. Protocol 1 heartbeats remain compatible for presence but cannot receive work.
6
6
 
7
7
  ## Prerequisites
8
8
 
package/dist/execution.js CHANGED
@@ -22,9 +22,21 @@ function changesRequestedFeedback(summary) {
22
22
  return null;
23
23
  }
24
24
  }
25
+ /**
26
+ * A network fault reaching the code host. Mirrors FORGE_TRANSPORT_PATTERN in the control plane's
27
+ * failures.ts — Bridge cannot import from there, so test/failure-classification.test.ts pins the two
28
+ * together rather than trusting them to stay in step.
29
+ */
30
+ export const FORGE_TRANSPORT_PATTERN = /unable to access '?https?:\/\/|error in the http2 framing layer|could not resolve host|connection (?:reset|timed out|refused)|\bcurl\b.*\b(?:52|55|56|92)\b|remote end hung up unexpectedly|\brpc failed\b|tls handshake|network is unreachable|operation timed out/i;
31
+ export function forgeTransportFailure(message) {
32
+ return FORGE_TRANSPORT_PATTERN.test(message);
33
+ }
25
34
  /** Failures that require an operator/configuration change must never burn the remaining attempts. */
26
35
  export function retryableAgentFailure(message) {
27
- return !/unknown (?:option|argument)|unrecognized (?:option|argument)|not logged in|no login|not authenticated|login required|\bENOENT\b|could not verify the installed CLI|requires local fuel|No Bridge-mapped|preflight/i.test(message);
36
+ // `cannot enforce` is a driver projection refusing a class it structurally cannot bound (Pi and
37
+ // observe_network). No number of retries on that lane changes it, and retrying an immutable input
38
+ // is how one assignment burned ~80 attempts. Fail closed; the message names the drivers that work.
39
+ return !/unknown (?:option|argument)|unrecognized (?:option|argument)|not logged in|no login|not authenticated|login required|\bENOENT\b|could not verify the installed CLI|requires local fuel|No Bridge-mapped|cannot enforce|preflight/i.test(message);
28
40
  }
29
41
  const assignmentSchema = z.object({
30
42
  id: z.string().uuid(),
@@ -54,7 +66,6 @@ const taskSpecSchema = z.object({
54
66
  execution_class: z.enum(["observe", "observe_network", "publish_artifact", "verify", "mutate_repo"]).optional(),
55
67
  lands: z.boolean().optional(),
56
68
  privileged_grants: z.array(z.string()).optional(),
57
- external_network: z.boolean().optional(),
58
69
  instruction: z.string().optional(),
59
70
  evidence_standard: z.unknown().optional(),
60
71
  working_language: z.enum(["en", "zh"]).optional(),
@@ -519,9 +530,13 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
519
530
  }
520
531
  catch (error) {
521
532
  const message = error instanceof Error ? error.message : "Agent delivery report was invalid";
522
- // Contract/tooling/credential failures need a human fix. Automatic retry would repeat the same
523
- // state immediately and spend another attempt before the operator can change anything.
524
- await queueTerminal(client, taskId, { action: "fail", body: { error: message, retryable: false, idempotency_key: `bridge:invalid-delivery:${active.attemptId}` } });
533
+ // Contract/tooling/credential failures need a human fix: automatic retry would repeat the same
534
+ // state immediately and spend another attempt before the operator can change anything. A lost
535
+ // connection to the code host is the opposite nothing here is wrong and the next attempt can
536
+ // succeed — and calling it non-retryable is what reported a GitHub HTTP/2 flake on `git push`
537
+ // as the agent failing its contract.
538
+ const transport = forgeTransportFailure(message);
539
+ await queueTerminal(client, taskId, { action: "fail", body: { error: message, retryable: transport, idempotency_key: `bridge:invalid-delivery:${active.attemptId}` } });
525
540
  console.error(`Assignment ${taskId} could not produce a valid Delivery: ${redactSecrets(message)}`);
526
541
  const replyTail = reportText.slice(-8_000);
527
542
  console.error(`Assignment ${taskId} agent reply tail (${reportText.length} chars total, redacted): ${redactSecrets(replyTail) || "<empty>"}`);
@@ -706,10 +721,12 @@ async function prepareDelivery(client, attemptId, taskId, report) {
706
721
  };
707
722
  }
708
723
  export function validateDeliveryReport(report, spec, grants = []) {
709
- validateChangeScope(report, spec.change_scope ?? []);
710
724
  // Research findings live in the report. Do not demand a published pack URL for work_role=research
711
725
  // even when a planner mis-labeled the package as deliverable=artifact.
712
726
  const artifactDelivery = spec.deliverable === "artifact" && spec.work_role !== "research";
727
+ // Artifact packs are not git landings — do not demand head_commit when the planner put
728
+ // `.conduit/artifacts/**` in change_scope as a write hint (path-4 canary incident).
729
+ validateChangeScope(report, spec.change_scope ?? [], { requireHeadCommit: !artifactDelivery });
713
730
  if (artifactDelivery) {
714
731
  const published = report.evidence.some((item) => {
715
732
  if (!["preview", "research", "documentation"].includes(item.kind))
@@ -776,11 +793,15 @@ function referenceKind(kind) {
776
793
  return "document";
777
794
  return "other";
778
795
  }
779
- export function validateChangeScope(report, changeScope) {
796
+ export function validateChangeScope(report, changeScope, options = {}) {
780
797
  if (!changeScope.length)
781
798
  return;
782
- if (!report.head_commit)
799
+ const requireHeadCommit = options.requireHeadCommit !== false;
800
+ if (requireHeadCommit && !report.head_commit) {
783
801
  throw new Error("Repository changes require a delivered head commit");
802
+ }
803
+ if (!report.changes.length)
804
+ return;
784
805
  const paths = report.changes.map((change) => change.split(/\s+[—-]\s+/, 1)[0].trim().replaceAll("\\", "/"));
785
806
  const outside = paths.filter((path) => !path || path.startsWith("/") || path.split("/").includes("..") || !changeScope.some((scope) => pathMatchesScope(path, scope)));
786
807
  if (outside.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {