@miraland-labs/conduit-bridge 0.16.121 → 0.16.130
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 +1 -1
- package/dist/brief.js +2 -0
- package/dist/driver.js +7 -4
- package/dist/ensure-demonstration-evidence.js +98 -0
- package/dist/ensure-land-commit.js +1 -1
- package/dist/ensure-pull-request.js +1 -1
- package/dist/ensure-test-evidence.js +1 -1
- package/dist/execution-class.js +4 -1
- package/dist/execution.js +25 -6
- package/dist/failure-signal.js +1 -1
- package/dist/investigation.js +11 -4
- package/dist/land-git-reality.js +1 -1
- package/dist/on-shift-apply.js +16 -0
- package/package.json +1 -1
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.16.
|
|
5
|
+
**Package version:** `0.16.125` — the workspace brief reports `declared_demonstration` so the planner knows whether the repository can show itself (KERNEL.md §51). Earlier (0.16.124) — when a project declares `.conduit/demonstrate`, Bridge runs that bounded command at the delivered head and attaches a stamped `preview` row (KERNEL.md §47). Earlier (0.16.95) — Conduit-fuel lanes bind organization aliases (T144), the Anthropic base URL is the origin (T148), the investigation lane resolves its model (T154); a hosted container image lives under `hosted/` (docs/HOSTED_RUNNER.md). Earlier (0.16.27) — work-package execution budgets reach every agent turn, are bounded by the machine ceiling, and expose their effective source for operations. Local-fuel provider refusals make the affected lane unavailable instead of repeatedly spending attempts; an operator who changes the lane's account or plan can invalidate that observation with `ops quota-clear <driver>`. Bridge discovers bounded repository verification commands, including Make targets `test`, `check`, `verify`, `replay`, `typecheck`, `lint`, and `build`. `ops disconnect` asks before it removes the runner service and clears credentials; pass `--yes` to skip the question. `ops.env` is enrollment/bootstrap defaults only: an explicit workspace does not inherit an old repository, and project switches do not rewrite global project state. `ops enroll` exchanges a single-use token, registers the machine, binds project affinity, provisions fuel keys, brings detected drivers online, and starts the runner in one command. On Linux, `ops install` refuses any effective systemd drop-in. A switch intent remains queued until the target runner heartbeat proves Bound. After every Bridge publish, operators must re-run `ops install` (LaunchAgent pins an absolute `cli.js`).
|
|
6
6
|
|
|
7
7
|
## Prerequisites
|
|
8
8
|
|
package/dist/brief.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { isBoundedVerificationCommand } from "./execution-class.js";
|
|
6
|
+
import { readDeclaredDemonstration } from "./ensure-demonstration-evidence.js";
|
|
6
7
|
import { gitOriginUrl, headCommitOrNull } from "./git.js";
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
const MANIFESTS = [
|
|
@@ -39,6 +40,7 @@ export async function buildWorkspaceBrief(workspace) {
|
|
|
39
40
|
manifests,
|
|
40
41
|
verification: await discoverVerificationCommands(workspace, files),
|
|
41
42
|
declared_verification: declared,
|
|
43
|
+
declared_demonstration: await readDeclaredDemonstration(workspace),
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
/**
|
package/dist/driver.js
CHANGED
|
@@ -696,7 +696,7 @@ export function claudeUsage(message) {
|
|
|
696
696
|
*/
|
|
697
697
|
export const CLAUDE_CODE_KNOWN_MODELS = [
|
|
698
698
|
"fable", "opus", "sonnet", "haiku",
|
|
699
|
-
"claude-fable-5-1", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5",
|
|
699
|
+
"claude-fable-5-1", "claude-opus-5-5", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5",
|
|
700
700
|
];
|
|
701
701
|
export const claudeCodeDriver = {
|
|
702
702
|
name: "claude-code",
|
|
@@ -960,7 +960,8 @@ export function cursorRunArgs(input) {
|
|
|
960
960
|
args.push(...modelArgs(cursorDriver, input.model));
|
|
961
961
|
if (input.resumeSessionId)
|
|
962
962
|
args.push("--resume", input.resumeSessionId);
|
|
963
|
-
|
|
963
|
+
// The prompt goes on stdin, as it does for codex: Linux caps one argument at 128 KiB
|
|
964
|
+
// (MAX_ARG_STRLEN) and an assignment contract exceeds that, which spawned `E2BIG`.
|
|
964
965
|
return args;
|
|
965
966
|
}
|
|
966
967
|
/** Parse `agent --list-models` output: one "id - Label" line per model. */
|
|
@@ -999,7 +1000,9 @@ export const cursorDriver = {
|
|
|
999
1000
|
vendor: "cursor",
|
|
1000
1001
|
// Cursor's own first-party families only. `--list-models` also lists other vendors' models
|
|
1001
1002
|
// Cursor proxies at a much higher price (the "Other Models" allowance) — those are not owned.
|
|
1002
|
-
|
|
1003
|
+
// Grok 4.7 dropped the `cursor-` prefix (`grok-4.7-high`, 2026-09-26) but Cursor still lists it
|
|
1004
|
+
// under "Cursor Models", beside Grok 4.6 and Composer.
|
|
1005
|
+
owns: (id) => id.startsWith("cursor-grok-") || id.startsWith("grok-") || id.startsWith("composer-"),
|
|
1003
1006
|
},
|
|
1004
1007
|
async run(input) {
|
|
1005
1008
|
const fuelSource = input.fuelSource === "local" ? "local" : "conduit";
|
|
@@ -1047,7 +1050,7 @@ export const cursorDriver = {
|
|
|
1047
1050
|
capabilities: input.capabilities ?? [],
|
|
1048
1051
|
diagnosis: input.workRole === "diagnose",
|
|
1049
1052
|
});
|
|
1050
|
-
const configured = await withCursorPermissions(input.workspace, { allow: projected.allow, deny: projected.deny }, () => execute(executable, cursorRunArgs({ ...input, trustWorkspace, executionClass, force: projected.force }), input.workspace, agentTurnTimeoutMs(input), undefined, "local",
|
|
1053
|
+
const configured = await withCursorPermissions(input.workspace, { allow: projected.allow, deny: projected.deny }, () => execute(executable, cursorRunArgs({ ...input, trustWorkspace, executionClass, force: projected.force }), input.workspace, agentTurnTimeoutMs(input), undefined, "local", input.prompt, input.signal));
|
|
1051
1054
|
const { code, stdout, stderr } = configured;
|
|
1052
1055
|
const parsed = parseCursorOutput(stdout);
|
|
1053
1056
|
// Cursor prints no token or cost figure, so the run reports the one fact it can prove: it ran.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When the project declares how it shows itself, Bridge runs that command at the delivered head and
|
|
3
|
+
* attaches what it printed. The agent never authors this entry (KERNEL.md §47, "See it").
|
|
4
|
+
*
|
|
5
|
+
* `.conduit/demonstrate` is one bounded command, `#` comments allowed, absent by default. It runs
|
|
6
|
+
* with the same shape check, runner and bound as a verification command, and it is never a gate:
|
|
7
|
+
* a demonstration that fails, times out or prints nothing attaches as `unverifiable` with its
|
|
8
|
+
* reason, and cannot fail a criterion.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { runBoundedVerificationCommand } from "./ensure-test-evidence.js";
|
|
13
|
+
import { isRunnableVerificationCommand } from "./execution-class.js";
|
|
14
|
+
/** Fixed name the control plane reads to place the row on the decision card. */
|
|
15
|
+
export const DEMONSTRATION_ARTIFACT_NAME = "What it does (Bridge witness)";
|
|
16
|
+
export const DEMONSTRATION_DECLARATION_PATH = ".conduit/demonstrate";
|
|
17
|
+
/** The transcript is text the owner reads, not a log: the head, cut with a marker. */
|
|
18
|
+
export const DEMONSTRATION_MAX_CHARS = 4_000;
|
|
19
|
+
export function parseDeclaredDemonstration(text) {
|
|
20
|
+
for (const raw of text.split("\n")) {
|
|
21
|
+
const line = raw.split("#")[0].trim();
|
|
22
|
+
if (!line)
|
|
23
|
+
continue;
|
|
24
|
+
return isRunnableVerificationCommand(line) ? line : null;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
export async function readDeclaredDemonstration(workspace) {
|
|
29
|
+
try {
|
|
30
|
+
return parseDeclaredDemonstration(await readFile(join(workspace, DEMONSTRATION_DECLARATION_PATH), "utf8"));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null; // absent declaration, absent row
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function boundedTranscript(text) {
|
|
37
|
+
const trimmed = text.trim();
|
|
38
|
+
const cut = trimmed.length > DEMONSTRATION_MAX_CHARS;
|
|
39
|
+
const kept = cut ? trimmed.slice(0, DEMONSTRATION_MAX_CHARS) : trimmed;
|
|
40
|
+
return [...kept.split("\n"), ...(cut ? [`… cut at ${DEMONSTRATION_MAX_CHARS.toLocaleString("en-US")} characters …`] : [])];
|
|
41
|
+
}
|
|
42
|
+
export async function ensureDemonstrationEvidence(input) {
|
|
43
|
+
const command = await readDeclaredDemonstration(input.workspace);
|
|
44
|
+
if (!command)
|
|
45
|
+
return input.report;
|
|
46
|
+
const run = input.runCommand ?? runBoundedVerificationCommand;
|
|
47
|
+
const withoutPrior = input.report.evidence.filter((item) => item.name !== DEMONSTRATION_ARTIFACT_NAME);
|
|
48
|
+
const attach = (details, witness, uri) => ({
|
|
49
|
+
...input.report,
|
|
50
|
+
evidence: [...withoutPrior, {
|
|
51
|
+
kind: "preview",
|
|
52
|
+
name: DEMONSTRATION_ARTIFACT_NAME,
|
|
53
|
+
...(uri ? { uri } : {}),
|
|
54
|
+
details: [`$ ${command}`, ...details],
|
|
55
|
+
acceptance_criteria: [],
|
|
56
|
+
witness,
|
|
57
|
+
}],
|
|
58
|
+
});
|
|
59
|
+
// A demonstration is repository code. Running it must consume the same explicit authority as a
|
|
60
|
+
// verification command; merely finding the declaration cannot grant shell execution to an
|
|
61
|
+
// observe-only package.
|
|
62
|
+
if (!input.grants.includes("test_run")) {
|
|
63
|
+
return attach(["Not run: this package does not hold test_run authority."], "unverifiable");
|
|
64
|
+
}
|
|
65
|
+
// The delivered code may demonstrate itself, but it may not author the command that earns the
|
|
66
|
+
// Bridge stamp in the same delivery. The declaration becomes a trusted project property only
|
|
67
|
+
// after an earlier delivery landed it. A missing path witness fails closed for the same reason.
|
|
68
|
+
if (input.changedPaths === null || input.changedPaths.includes(DEMONSTRATION_DECLARATION_PATH)) {
|
|
69
|
+
return attach([
|
|
70
|
+
`Not run: this delivery changed ${DEMONSTRATION_DECLARATION_PATH}; a delivery cannot author its own Bridge-witnessed demonstration.`,
|
|
71
|
+
], "unverifiable");
|
|
72
|
+
}
|
|
73
|
+
let result;
|
|
74
|
+
try {
|
|
75
|
+
result = await run(command, input.workspace);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return attach([`Could not run: ${error instanceof Error ? error.message : String(error)}`], "unverifiable");
|
|
79
|
+
}
|
|
80
|
+
if (result.code !== 0) {
|
|
81
|
+
return attach([...boundedTranscript(result.stderr || result.stdout), `exit ${result.code}`], "unverifiable");
|
|
82
|
+
}
|
|
83
|
+
if (!result.stdout.trim())
|
|
84
|
+
return attach(["The demonstration printed nothing."], "unverifiable");
|
|
85
|
+
const transcript = boundedTranscript(result.stdout);
|
|
86
|
+
// A line that is an https:// URL is the project publishing where to look; it becomes the preview reference.
|
|
87
|
+
const link = transcript.map((line) => line.trim()).find((line) => {
|
|
88
|
+
if (!line.startsWith("https://"))
|
|
89
|
+
return false;
|
|
90
|
+
try {
|
|
91
|
+
return new URL(line).protocol === "https:";
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return attach(transcript, "bridge", link);
|
|
98
|
+
}
|
|
@@ -51,7 +51,7 @@ export async function ensureLandCommit(input) {
|
|
|
51
51
|
if (!requiresLandCommit({ grants: input.grants, spec: input.spec })) {
|
|
52
52
|
return { kind: "skipped" };
|
|
53
53
|
}
|
|
54
|
-
const base = input.spec.repository?.base_commit;
|
|
54
|
+
const base = input.baseCommit ?? input.spec.repository?.base_commit;
|
|
55
55
|
if (!base)
|
|
56
56
|
return { kind: "skipped" };
|
|
57
57
|
const countAhead = input.countCommitsAhead ?? commitsAheadOfBase;
|
|
@@ -263,7 +263,7 @@ export async function ensureDeliveryPullRequest(input) {
|
|
|
263
263
|
// files. GitHub then answers the pull-request call with an opaque 422 "No commits between main
|
|
264
264
|
// and <branch>" (initiative efa56ccd, attempt 874fa0e3). Prove the branch carries work first, and
|
|
265
265
|
// report the real condition instead of a forge validation error.
|
|
266
|
-
const base = input.spec.repository?.base_commit;
|
|
266
|
+
const base = input.baseCommit ?? input.spec.repository?.base_commit;
|
|
267
267
|
if (base) {
|
|
268
268
|
const ahead = await countAhead(input.workspace, base);
|
|
269
269
|
if (ahead === 0) {
|
|
@@ -101,7 +101,7 @@ export function verificationCommandsMentioned(text, offered = []) {
|
|
|
101
101
|
const source = text.slice(0, 20_000);
|
|
102
102
|
const verbatim = [...new Set(offered.map((command) => command.trim()))]
|
|
103
103
|
.filter((command) => isBoundedVerificationCommand(command) && textNamesCommand(source, command));
|
|
104
|
-
const pattern = /(?:^|[\s("'`])((?:npm run [A-Za-z0-9][A-Za-z0-9._:-]*|pnpm [A-Za-z0-9][A-Za-z0-9._:-]*|yarn [A-Za-z0-9][A-Za-z0-9._:-]*|cargo (?:test|check)|go test(?: \.\/\.\.\.)?|make [A-Za-z0-9][A-Za-z0-9._-]*|python3? -m [A-Za-z0-9_][\w.]*(?: [\w./=:-]*[./=-][\w./=:-]*)*|pytest(?: [\w./=-]*[./=-][\w./=-]*)?|python3? [\w./-]+\.py(?: [\w./=:-]*[./=-][\w./=:-]*)*|(?:\.\/gradlew|gradle|\.\/mvnw|mvn) test|Rscript tests\/testthat\.R|Rscript -e testthat::test_local\(\)))(?=$|[\s"'`),.;:])/g;
|
|
104
|
+
const pattern = /(?:^|[\s("'`])((?:npm run [A-Za-z0-9][A-Za-z0-9._:-]*|pnpm [A-Za-z0-9][A-Za-z0-9._:-]*|yarn [A-Za-z0-9][A-Za-z0-9._:-]*|cargo (?:test|check|clippy)|go test(?: \.\/\.\.\.)?|make [A-Za-z0-9][A-Za-z0-9._-]*|python3? -m [A-Za-z0-9_][\w.]*(?: [\w./=:-]*[./=-][\w./=:-]*)*|pytest(?: [\w./=-]*[./=-][\w./=-]*)?|python3? [\w./-]+\.py(?: [\w./=:-]*[./=-][\w./=:-]*)*|(?:\.\/gradlew|gradle|\.\/mvnw|mvn) test|Rscript tests\/testthat\.R|Rscript -e testthat::test_local\(\)))(?=$|[\s"'`),.;:])/g;
|
|
105
105
|
const mentions = [...new Set([...source.matchAll(pattern)].map((match) => match[1]))]
|
|
106
106
|
.filter((mention) => isBoundedVerificationCommand(mention)
|
|
107
107
|
&& !verbatim.some((command) => command === mention || command.startsWith(`${mention} `)));
|
package/dist/execution-class.js
CHANGED
|
@@ -13,7 +13,10 @@ export function isBoundedVerificationCommand(command) {
|
|
|
13
13
|
// A `*` is a legal character in a Python argument (`-p test_*.py` is how a repository spells its
|
|
14
14
|
// own gate). Bridge starts a gate with execFile and no shell, so the star stays literal and no
|
|
15
15
|
// expansion can happen. Refusing it only dropped the declaration, with no reason given.
|
|
16
|
-
|
|
16
|
+
// Cargo takes flags the same way: acme-sim registered `cargo clippy --all-targets -- -D warnings`
|
|
17
|
+
// on 2026-09-25, the rule admitted only a bare `cargo test|check`, and five deliveries shipped
|
|
18
|
+
// clippy failures because the gate was dropped before any attempt could run it.
|
|
19
|
+
return /^(npm run [A-Za-z0-9][A-Za-z0-9._:-]*|pnpm [A-Za-z0-9][A-Za-z0-9._:-]*|yarn [A-Za-z0-9][A-Za-z0-9._:-]*|cargo (test|check|clippy)(?: [\w./=:-]+)*|go test(?: \.\/\.\.\.)?|make [A-Za-z0-9][A-Za-z0-9._-]*|python3? -m [A-Za-z0-9_][\w.]*(?: [\w./=:*-]+)*|pytest(?: [\w./=*-]+)?|python3? [\w./-]+\.py(?: [\w./=:-]+)*|(?:\.\/gradlew|gradle|\.\/mvnw|mvn) test|Rscript tests\/testthat\.R|Rscript -e testthat::test_local\(\))$/.test(command);
|
|
17
20
|
}
|
|
18
21
|
/** Shell commands each grant authorizes — mapped per driver so they cannot drift. */
|
|
19
22
|
export const branchCreateCommands = [
|
package/dist/execution.js
CHANGED
|
@@ -27,6 +27,7 @@ import { agentClaimsRepositoryWork, claimsVsGitMismatch, isLandTreeEmpty, report
|
|
|
27
27
|
const execFileAsync = promisify(execFile);
|
|
28
28
|
import { boundedTail, captureVerificationFailure, detectGateRanNothing, ensureTestEvidence, needsTestEvidence, runBoundedVerificationCommand, verificationEvidenceCommands, VerificationGateRanNothingError } from "./ensure-test-evidence.js";
|
|
29
29
|
import { ensureChangeEvidence } from "./ensure-change-evidence.js";
|
|
30
|
+
import { ensureDemonstrationEvidence } from "./ensure-demonstration-evidence.js";
|
|
30
31
|
import { ensureNormativeEvidence } from "./ensure-normative-evidence.js";
|
|
31
32
|
import { changedPathsSince, compareWorktreeState, diffStatSince, worktreeStateFingerprint, worktreeWrittenPaths } from "./git-witness.js";
|
|
32
33
|
import { assertNormativeRefsMaterialized, checkNormativeMarkers, materializeNormativeRefs, NORMATIVE_MARKER_PREFIX, NORMATIVE_REF_PREFIX, parseNormativeRefs, } from "./normative-refs.js";
|
|
@@ -1179,7 +1180,11 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1179
1180
|
// diffing from the requested base would report commits that predate the agent as its work.
|
|
1180
1181
|
// T138: a rework carries the first revision's resolved start, because its worktree starts at the
|
|
1181
1182
|
// previous delivery's head. That lets a prose-only rework witness the original pack while still
|
|
1182
|
-
// including any new rework changes.
|
|
1183
|
+
// including any new rework changes. A conflict rework is different: it is pinned to the live base
|
|
1184
|
+
// tip and starts fresh there, so the control plane sends no carried base and the requested base --
|
|
1185
|
+
// the pinned tip -- is the delivery base. Diffing from the first revision's start would report
|
|
1186
|
+
// every commit the base gained in between as the agent's own work. Older contracts fall back to
|
|
1187
|
+
// the T136 rule.
|
|
1183
1188
|
const rework = task.delivery_state === "changes_requested" || Boolean(executionContract.prior_head_commit);
|
|
1184
1189
|
const deliveryBaseCommit = executionContract.delivery_base_commit
|
|
1185
1190
|
?? (rework
|
|
@@ -1803,7 +1808,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1803
1808
|
watchdog.phase("land");
|
|
1804
1809
|
if (requiresLandCommit({ grants, spec })) {
|
|
1805
1810
|
try {
|
|
1806
|
-
const land = await ensureLandCommit({ workspace: attemptWorkspace, spec, grants });
|
|
1811
|
+
const land = await ensureLandCommit({ workspace: attemptWorkspace, spec, grants, baseCommit: deliveryBaseCommit });
|
|
1807
1812
|
if (land.kind === "committed") {
|
|
1808
1813
|
await client.attemptRequest(taskId, "progress", {
|
|
1809
1814
|
phase: "preparing_delivery",
|
|
@@ -1825,6 +1830,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1825
1830
|
spec,
|
|
1826
1831
|
grants,
|
|
1827
1832
|
agentReply: reportText,
|
|
1833
|
+
baseCommit: deliveryBaseCommit,
|
|
1828
1834
|
})) {
|
|
1829
1835
|
const continuation = await runLandContinuationTurn({
|
|
1830
1836
|
config,
|
|
@@ -1859,7 +1865,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1859
1865
|
report = parseAgentReport(reportText, spec.acceptance ?? []);
|
|
1860
1866
|
}
|
|
1861
1867
|
catch (error) {
|
|
1862
|
-
const gitEmpty = await isLandTreeEmpty({ workspace: attemptWorkspace, spec, grants });
|
|
1868
|
+
const gitEmpty = await isLandTreeEmpty({ workspace: attemptWorkspace, spec, grants, baseCommit: deliveryBaseCommit });
|
|
1863
1869
|
const claimsWork = agentClaimsRepositoryWork(reportText);
|
|
1864
1870
|
if (requiresLandCommit({ grants, spec }) && gitEmpty && claimsWork) {
|
|
1865
1871
|
if (!landContinuationUsed) {
|
|
@@ -2059,7 +2065,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
2059
2065
|
if (!landContinuationUsed
|
|
2060
2066
|
&& requiresLandCommit({ grants, spec })
|
|
2061
2067
|
&& reportClaimsRepositoryWork(report)
|
|
2062
|
-
&& await isLandTreeEmpty({ workspace: attemptWorkspace, spec, grants })) {
|
|
2068
|
+
&& await isLandTreeEmpty({ workspace: attemptWorkspace, spec, grants, baseCommit: deliveryBaseCommit })) {
|
|
2063
2069
|
const continuation = await runLandContinuationTurn({
|
|
2064
2070
|
config,
|
|
2065
2071
|
client,
|
|
@@ -2129,6 +2135,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
2129
2135
|
maxDurationMs: workPackage?.max_duration_ms,
|
|
2130
2136
|
resumeSessionId: agentSessionId,
|
|
2131
2137
|
landContinuationUsed,
|
|
2138
|
+
landBaseCommit: deliveryBaseCommit,
|
|
2132
2139
|
});
|
|
2133
2140
|
// Mechanical land path: the agent only pushes; Bridge pushes again if needed and the control
|
|
2134
2141
|
// plane opens the change request with the organization's forge connection.
|
|
@@ -2140,6 +2147,7 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
2140
2147
|
report,
|
|
2141
2148
|
spec,
|
|
2142
2149
|
grants,
|
|
2150
|
+
baseCommit: deliveryBaseCommit,
|
|
2143
2151
|
title: task.objective,
|
|
2144
2152
|
repositoryFingerprint: executionContract.repository_fingerprint,
|
|
2145
2153
|
priorPullRequestUrl: executionContract.prior_pull_request_url,
|
|
@@ -2166,6 +2174,16 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
2166
2174
|
grants,
|
|
2167
2175
|
changedPaths: actualPaths,
|
|
2168
2176
|
});
|
|
2177
|
+
// §47 "See it": the project's own demonstration at the delivered head, never a gate.
|
|
2178
|
+
const beforeDemonstration = report;
|
|
2179
|
+
report = await ensureDemonstrationEvidence({
|
|
2180
|
+
workspace: attemptWorkspace,
|
|
2181
|
+
report,
|
|
2182
|
+
grants,
|
|
2183
|
+
changedPaths: actualPaths,
|
|
2184
|
+
});
|
|
2185
|
+
if (report !== beforeDemonstration)
|
|
2186
|
+
await discardWorktreeWrites(attemptWorkspace);
|
|
2169
2187
|
const integrationCheck = await checkIntegrationObligations(attemptWorkspace, parseIntegrationObligations(spec.integration_obligations));
|
|
2170
2188
|
if (!integrationCheck.ok) {
|
|
2171
2189
|
throw new Error(`${INTEGRATION_OBLIGATION_PREFIX}: ${integrationCheck.failures.join("; ")}`);
|
|
@@ -2371,6 +2389,7 @@ async function finalizeRepositoryLand(input) {
|
|
|
2371
2389
|
workspace: input.workspace,
|
|
2372
2390
|
spec: input.spec,
|
|
2373
2391
|
grants: input.grants,
|
|
2392
|
+
baseCommit: input.landBaseCommit,
|
|
2374
2393
|
});
|
|
2375
2394
|
if (land.kind === "committed") {
|
|
2376
2395
|
await input.client.attemptRequest(input.taskId, "progress", {
|
|
@@ -2392,7 +2411,7 @@ async function finalizeRepositoryLand(input) {
|
|
|
2392
2411
|
throw error;
|
|
2393
2412
|
}
|
|
2394
2413
|
if (input.landContinuationUsed) {
|
|
2395
|
-
throw new AgentNoLandCommitError(input.spec.repository?.base_commit ?? "unknown");
|
|
2414
|
+
throw new AgentNoLandCommitError(input.landBaseCommit ?? input.spec.repository?.base_commit ?? "unknown");
|
|
2396
2415
|
}
|
|
2397
2416
|
const continuation = await runLandContinuationTurn({
|
|
2398
2417
|
config: input.config,
|
|
@@ -2664,7 +2683,7 @@ async function prepareDelivery(client, attemptId, taskId, report) {
|
|
|
2664
2683
|
metadata: {
|
|
2665
2684
|
details: item.details,
|
|
2666
2685
|
acceptance_criteria: item.acceptance_criteria,
|
|
2667
|
-
...(item.witness
|
|
2686
|
+
...(item.witness ? { witness: item.witness } : {}),
|
|
2668
2687
|
},
|
|
2669
2688
|
idempotency_key: `bridge:artifact:${attemptId}:${index + 1}`,
|
|
2670
2689
|
});
|
package/dist/failure-signal.js
CHANGED
|
@@ -117,7 +117,7 @@ export const BRIDGE_CAUSES = {
|
|
|
117
117
|
workspace_head_changed: { class: "environment", disposition: "hold", responsible_party: "computer_operator", message: "The checkout changed after Conduit prepared the assignment.", next_action: "Let the checkout settle, then recheck readiness." },
|
|
118
118
|
base_not_ancestor: { class: "environment", disposition: "hold", responsible_party: "computer_operator", message: "Conduit could not prepare the required project version on this computer.", next_action: "No Git work is required from the product owner. Keep the computer and Bridge online, then try again so Bridge can refresh the project. If the hold returns, the computer operator should check Bridge's repository access." },
|
|
119
119
|
workspace_dirty: { class: "environment", disposition: "hold", responsible_party: "computer_operator", message: "The source workspace has uncommitted changes.", next_action: "Commit, stash, or discard the local changes, then recheck readiness." },
|
|
120
|
-
missing_verification: { class: "environment", disposition: "hold", responsible_party: "conduit", message: "This checkout has no verification commands Conduit can run for this package.", next_action: "
|
|
120
|
+
missing_verification: { class: "environment", disposition: "hold", responsible_party: "conduit", message: "This checkout has no verification commands Conduit can run for this package.", next_action: "Open the initiative and request a revision: Conduit re-plans this package without test evidence this checkout cannot produce. You never author test commands yourself." },
|
|
121
121
|
verification_unwitnessable: { class: "environment", disposition: "hold", responsible_party: "computer_operator", message: "The project's verification command runs but prints nothing, so test evidence cannot be witnessed.", next_action: "Fix the verification recipe in the checkout so it prints real output, then recheck readiness. You do not need to author test commands for the agent." },
|
|
122
122
|
execution_contract_failed: { class: "contract", disposition: "rework", responsible_party: "conduit", message: "The agent could not satisfy the approved execution contract.", next_action: "Review the last run and rework the same contract; change the plan only if its authority or outcome is wrong." },
|
|
123
123
|
agent_no_land_commit: { class: "contract", disposition: "rework", responsible_party: "conduit", message: "The agent finished without committing the repository changes this package requires.", next_action: "Retry implement. Conductor will brief a land-focused recovery attempt — you do not need to edit technical constraints." },
|
package/dist/investigation.js
CHANGED
|
@@ -790,18 +790,25 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
790
790
|
: undefined;
|
|
791
791
|
// T126: the prompt names the commands of the repository this lane opened — not the control
|
|
792
792
|
// plane's idea of them — and carries the diff stat Bridge computed, since the lane cannot run git.
|
|
793
|
+
// Discover those commands from the delivered worktree, not the source checkout: a greenfield
|
|
794
|
+
// delivery can introduce its manifest and test gate in the commit this investigation verifies.
|
|
793
795
|
const deliveryVerification = isDeliveryVerification(assignment);
|
|
796
|
+
const investigationBrief = deliveryVerification
|
|
797
|
+
? await buildWorkspaceBrief(attemptWorkspace)
|
|
798
|
+
: liveBrief;
|
|
799
|
+
const verificationCommands = investigationBrief?.verification ?? [];
|
|
800
|
+
const declaredVerificationCommands = investigationBrief?.declared_verification ?? [];
|
|
794
801
|
const gatesRun = deliveryVerification
|
|
795
802
|
? await runDeliveryVerificationGates({
|
|
796
803
|
workspace: attemptWorkspace,
|
|
797
|
-
verificationCommands
|
|
798
|
-
declaredVerificationCommands
|
|
804
|
+
verificationCommands,
|
|
805
|
+
declaredVerificationCommands,
|
|
799
806
|
acceptance: assignment.acceptance ?? [],
|
|
800
807
|
runCommand: options.runVerificationCommand,
|
|
801
808
|
})
|
|
802
809
|
: [];
|
|
803
810
|
const context = {
|
|
804
|
-
verificationCommands
|
|
811
|
+
verificationCommands,
|
|
805
812
|
diffStat: assignment.base_commit ? await diffStatSince(attemptWorkspace, assignment.base_commit) : null,
|
|
806
813
|
gatesRun,
|
|
807
814
|
};
|
|
@@ -815,7 +822,7 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
815
822
|
workspace: attemptWorkspace,
|
|
816
823
|
...(selection.model ? { model: selection.model } : {}),
|
|
817
824
|
grants: assignment.grants,
|
|
818
|
-
verificationCommands
|
|
825
|
+
verificationCommands,
|
|
819
826
|
deliverable: "repository",
|
|
820
827
|
// The same signal the read-only diagnosis lane uses; it is what denies edits in the driver.
|
|
821
828
|
workRole: "diagnose",
|
package/dist/land-git-reality.js
CHANGED
|
@@ -33,7 +33,7 @@ async function listUncommittedPaths(workspace) {
|
|
|
33
33
|
export async function isLandTreeEmpty(input) {
|
|
34
34
|
if (!requiresLandCommit({ grants: input.grants, spec: input.spec }))
|
|
35
35
|
return false;
|
|
36
|
-
const base = input.spec.repository?.base_commit;
|
|
36
|
+
const base = input.baseCommit ?? input.spec.repository?.base_commit;
|
|
37
37
|
if (!base)
|
|
38
38
|
return false;
|
|
39
39
|
const countAhead = input.countCommitsAhead ?? commitsAheadOfBase;
|
package/dist/on-shift-apply.js
CHANGED
|
@@ -8,6 +8,7 @@ import { expandOpsValue } from "./ops.js";
|
|
|
8
8
|
import { ensureCheckout } from "./checkout.js";
|
|
9
9
|
import { bootstrapManagedWorkspace } from "./workspace-bootstrap.js";
|
|
10
10
|
import { managedWorkspacePath } from "./managed-workspace-path.js";
|
|
11
|
+
import { refreshSourceWorkspace } from "./workspace-refresh.js";
|
|
11
12
|
export { managedWorkspacePath } from "./managed-workspace-path.js";
|
|
12
13
|
function pathsEqual(left, right) {
|
|
13
14
|
return resolve(expandOpsValue(left)) === resolve(expandOpsValue(right));
|
|
@@ -32,6 +33,21 @@ export async function switchManagedWorkspace(managedRoot, repositoryUrl, deps =
|
|
|
32
33
|
if (result === "cloned") {
|
|
33
34
|
console.log(`Cloned ${repositoryUrl} into ${target}`);
|
|
34
35
|
(deps.bootstrapWorkspace ?? bootstrapManagedWorkspace)(target);
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
// The runner's own refresh follows only the workspace it was started on. A managed checkout it
|
|
39
|
+
// switches into was never refreshed: acme-sim stayed shallow at a five-day-old main on
|
|
40
|
+
// 2026-09-25 while every claim read it. Follow origin here, before the claim reads the checkout.
|
|
41
|
+
// A dirty or diverged checkout is not moved; the attempt worktree refuses a dirty source.
|
|
42
|
+
const refresh = await refreshSourceWorkspace(target).catch(() => null);
|
|
43
|
+
if (refresh?.state === "fast_forwarded") {
|
|
44
|
+
console.log(`Managed workspace ${target} fast-forwarded to origin/${refresh.branch}.`);
|
|
45
|
+
}
|
|
46
|
+
else if (refresh?.state === "diverged") {
|
|
47
|
+
console.error(`Managed workspace ${target} cannot fast-forward: ${refresh.local_commits} local-only commit(s) on ${refresh.branch}. Park or push them.`);
|
|
48
|
+
}
|
|
49
|
+
else if (refresh?.state !== "up_to_date") {
|
|
50
|
+
console.error(`Managed workspace ${target} could not follow origin (dirty tree, detached HEAD, or fetch failed).`);
|
|
35
51
|
}
|
|
36
52
|
return target;
|
|
37
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.130",
|
|
4
4
|
"description": "Conduit Bridge CLI \u2014 join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|