From 57e74c4440a45f982e7619a157aad90e266b3f5a Mon Sep 17 00:00:00 2001 From: Mike Clay Date: Wed, 29 Jul 2026 08:21:44 +0100 Subject: [PATCH 1/2] docs(harness): point the repo binding at the git derivation, not workspace prose Four harness strings still told the caller the host repo comes from the user or the workspace AGENTS.md -- the provenance #345 demotes to a fallback. Each now names version-control::resolve-host-repo as the source and states the two conditions under which prose applies at all: a workspace that is not a git repo, or a host with no origin remote. The urgent one is workflow-tools.ts, inside the discover return. It is the first surface an orchestrator reads, and it sits directly above the rewritten bootstrap body -- so it contradicted the new contract before any session existed, re-inviting the silent misbinding #345 exists to remove. - src/tools/workflow-tools.ts -- the discover repo_binding line - src/tools/resource-tools.ts -- the start_session tool description - src/utils/session/scope.ts -- the multi-root "repo is required" error - docs/ide-setup.md -- the same claim on a user-facing surface, beyond the three the issue enumerates but the same defect Closes the harness-strings half of #348 (findings register MH-4). The submodule bump and walk re-baseline stay open: #345 is unmerged, and origin/workflows is still f84fe02b -- byte-identical to the pinned corpusSha -- so there is no commit to bump to yet. Refs #348, #345 --- docs/ide-setup.md | 2 +- src/tools/resource-tools.ts | 2 +- src/tools/workflow-tools.ts | 2 +- src/utils/session/scope.ts | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/ide-setup.md b/docs/ide-setup.md index 1be7b480e..9cd0143ea 100644 --- a/docs/ide-setup.md +++ b/docs/ide-setup.md @@ -49,7 +49,7 @@ Pass `session_index` from `start_session` on every authenticated workflow-server That is enough. `discover` returns the live bootstrap steps (schema fetch → bind `repo` → `start_session` → `get_workflow`). Do not copy the protocol into IDE rules. -Always pass `repo: "owner/repo"` on `start_session` (from the user or workspace `AGENTS.md` / `CLAUDE.md`). Agents do not special-case server topology. +Always pass `repo: "owner/repo"` on `start_session`, derived from git via `version-control::resolve-host-repo` — the origin remote of the outermost superproject that claims the workspace checkout. The user and the workspace `AGENTS.md` / `CLAUDE.md` are fallback sources only where that derivation yields nothing: a workspace that is not a git repo, or a host with no origin remote. Agents do not special-case server topology. ## Verify diff --git a/src/tools/resource-tools.ts b/src/tools/resource-tools.ts index 359eceaf6..5b560521d 100644 --- a/src/tools/resource-tools.ts +++ b/src/tools/resource-tools.ts @@ -103,7 +103,7 @@ export function registerResourceTools(server: McpServer, config: ServerConfig): description: 'Start or resume the top-level workflow session. Returns `session_index`, workflow metadata, and canonical `planning_folder_path`. ' + 'Pass `planning_folder` as an absolute path (basename = slug). ' + - 'Always pass `repo` as owner/repo (from the user or workspace AGENTS.md) — stored on session.json#repo. ' + + 'Always pass `repo` as owner/repo, derived from git via `version-control::resolve-host-repo` (origin remote of the outermost claiming superproject); the user or workspace AGENTS.md is a fallback only when the workspace is not a git repo or has no origin remote. Stored on session.json#repo. ' + 'Omit planning_folder for a transient meta bootstrap. Children use `dispatch_child`, not this tool. ' + '`context_mode: "persistent"` is ONLY for solo (same agent context; no worker spawn); omit/`"fresh"` for disposable workers.', inputSchema: z diff --git a/src/tools/workflow-tools.ts b/src/tools/workflow-tools.ts index 6bc815c0c..deb383d84 100644 --- a/src/tools/workflow-tools.ts +++ b/src/tools/workflow-tools.ts @@ -323,7 +323,7 @@ export function registerWorkflowTools(server: McpServer, config: ServerConfig): const lines = [ `server: ${config.serverName}`, `version: ${config.serverVersion}`, - 'repo_binding: required — pass repo: "owner/repo" on start_session (from user or workspace AGENTS.md)', + 'repo_binding: required — pass repo: "owner/repo" on start_session, derived from git via version-control::resolve-host-repo (the origin remote of the outermost superproject that claims the workspace checkout). The user or workspace AGENTS.md is a fallback only where that derivation yields nothing: a workspace that is not a git repo, or a host with no origin remote.', ]; if (bootstrapResult.success) { lines.push('', bootstrapResult.value.content); diff --git a/src/utils/session/scope.ts b/src/utils/session/scope.ts index 5e155bce1..13f4cba55 100644 --- a/src/utils/session/scope.ts +++ b/src/utils/session/scope.ts @@ -236,7 +236,9 @@ export function resolveSessionRoot( if (scope.mode === 'multi') { throw new Error( 'start_session: repo is required when the server is bound to a projects multi-root ' + - '(HOST_PROJECTS_ROOT). Pass repo: "owner/repo" (from the user or workspace AGENTS.md). ' + + '(HOST_PROJECTS_ROOT). Pass repo: "owner/repo" derived from git via version-control::resolve-host-repo ' + + '(origin remote of the outermost claiming superproject); the user or workspace AGENTS.md is a fallback ' + + 'only when the workspace is not a git repo or has no origin remote. ' + 'Planning lives at /.engineering/artifacts/planning/ under that root.', ); } From a7b12630b4c4810b4df69043aa95f23eb03be88f Mon Sep 17 00:00:00 2001 From: Mike Clay Date: Wed, 29 Jul 2026 08:52:12 +0100 Subject: [PATCH 2/2] chore(corpus): bump workflows to the merged #345 and re-baseline the walk Moves the workflows submodule pointer from f84fe02b to bd9c03df -- the merge commit of #345, 45 files -- and refreshes everything that pointer invalidates, in one commit, as tests/e2e/__snapshots__/corpus-sha.json and scripts/stamp-corpus-baseline.ts both require. Walk re-baseline (vitest run -u, then baseline:stamp): - corpus-sha.json f84fe02b -> bd9c03df - snapshot.test.ts.snap: one new step, derive-host-repo, before resolve-repo-root in all six policies. That is #345 splitting the old resolve-repo-root in two -- derive-host-repo binds the shared version-control::resolve-host-repo, then resolve-repo-root assembles component identity (findings register WC-1). Nothing else drifted. Two further failures the pointer move surfaced, both corpus-driven: - tests/reference-delivery.test.ts used work-package as its negative case for the inherited activity_rules block, on the premise that work-package declared only orchestrator-scoped rules.workflow. #345 adds a rules.activity bucket to work-package, so start-work-package now carries that block and it collapses to an unchanged marker on a byte-identical refetch -- the server behaving correctly, and what the dedicated case below it already asserts. Assertion and stale comment updated. - binding-fidelity gained one untriaged finding: {target_path} at substrate-node-security-audit/techniques/write-report.md:90 has no producer. #345 renames the output target_path -> component_path on both detect-repo-type and select-target-component, which were the corpus's only producers of that name; the audit workflow declares no target_path variable and binds neither technique, so its read had been resolving against those unrelated meta declarations all along. The rename removed the cover, it did not change how that run behaves. Verified new by running the guard at both shas: 0 untriaged at f84fe02b, 1 at bd9c03df. Triaged fix-later / undeclared-seed, which describes this state exactly -- the value is not state, so the agent improvises it at the point of use. The real repair is to declare and seed it in the corpus, which is a change on the workflows lineage, not on this branch. Verification: 17/17 guards pass; 742 tests pass. The one red test, session-crypto EACCES-when-not-writable, fails identically on unmodified main under the same sandbox (chmod does not block writes for that uid) and is unrelated to this change. Closes #348 --- scripts/binding-fidelity-triage.json | 7 +++++++ tests/e2e/__snapshots__/corpus-sha.json | 2 +- tests/e2e/__snapshots__/snapshot.test.ts.snap | 6 ++++++ tests/reference-delivery.test.ts | 9 +++++---- workflows | 2 +- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/scripts/binding-fidelity-triage.json b/scripts/binding-fidelity-triage.json index 60f9f118d..6f7291531 100644 --- a/scripts/binding-fidelity-triage.json +++ b/scripts/binding-fidelity-triage.json @@ -1315,6 +1315,13 @@ "verdict": "harmless", "rationale": "artifact-template-placeholder" }, + { + "check": "read-resolution", + "site": "substrate-node-security-audit/techniques/write-report.md:90", + "detail": "{target_path} has no producer (declared id / $-local / workflow var / set-target)", + "verdict": "fix-later", + "rationale": "undeclared-seed" + }, { "check": "read-resolution", "site": "work-package/techniques/manage-git/TECHNIQUE.md:25", diff --git a/tests/e2e/__snapshots__/corpus-sha.json b/tests/e2e/__snapshots__/corpus-sha.json index acc9a392d..821f87142 100644 --- a/tests/e2e/__snapshots__/corpus-sha.json +++ b/tests/e2e/__snapshots__/corpus-sha.json @@ -1,4 +1,4 @@ { - "corpusSha": "f84fe02b12f9617f401767b9b96f329d8c13225c", + "corpusSha": "bd9c03dfd549aa442047aae0488f7188338feb90", "note": "Corpus commit the committed walk snapshots were generated against. Update it in the same commit that bumps the workflows submodule and re-baselines the walk (npm run baseline:stamp)." } diff --git a/tests/e2e/__snapshots__/snapshot.test.ts.snap b/tests/e2e/__snapshots__/snapshot.test.ts.snap index 3adeb9283..5fb3d5381 100644 --- a/tests/e2e/__snapshots__/snapshot.test.ts.snap +++ b/tests/e2e/__snapshots__/snapshot.test.ts.snap @@ -53,6 +53,7 @@ exports[`work-package walk snapshots (baseline) > [default] matches committed ba "stepsExecuted": [ "announce-start", "detect-review-mode", + "derive-host-repo", "resolve-repo-root", "analyze-repo-with-gitnexus", "verify-signing-precondition", @@ -484,6 +485,7 @@ exports[`work-package walk snapshots (baseline) > [elicitation-only] matches com "stepsExecuted": [ "announce-start", "detect-review-mode", + "derive-host-repo", "resolve-repo-root", "analyze-repo-with-gitnexus", "verify-signing-precondition", @@ -982,6 +984,7 @@ exports[`work-package walk snapshots (baseline) > [full-workflow] matches commit "stepsExecuted": [ "announce-start", "detect-review-mode", + "derive-host-repo", "resolve-repo-root", "analyze-repo-with-gitnexus", "verify-signing-precondition", @@ -1507,6 +1510,7 @@ exports[`work-package walk snapshots (baseline) > [research-only] matches commit "stepsExecuted": [ "announce-start", "detect-review-mode", + "derive-host-repo", "resolve-repo-root", "analyze-repo-with-gitnexus", "verify-signing-precondition", @@ -1978,6 +1982,7 @@ exports[`work-package walk snapshots (baseline) > [review-mode] matches committe "announce-derived-review-mode", "ingest-prior-feedback", "seed-review-mode-outcomes", + "derive-host-repo", "resolve-repo-root", "analyze-repo-with-gitnexus", "verify-signing-precondition", @@ -2353,6 +2358,7 @@ exports[`work-package walk snapshots (baseline) > [skip-optional] matches commit "stepsExecuted": [ "announce-start", "detect-review-mode", + "derive-host-repo", "resolve-repo-root", "analyze-repo-with-gitnexus", "verify-signing-precondition", diff --git a/tests/reference-delivery.test.ts b/tests/reference-delivery.test.ts index 5821b6e3e..975e80e9d 100644 --- a/tests/reference-delivery.test.ts +++ b/tests/reference-delivery.test.ts @@ -219,11 +219,12 @@ describe('reference-not-repeat delivery (B1)', () => { expect(isUnchangedMarker(value), `expected marker for ${key}`).toBe(true); } expect(isUnchangedMarker(second.bundle['rules'])).toBe(true); - // The activity body itself is still delivered. (`work-package` declares only - // orchestrator-scoped `rules.workflow`, so it carries no inherited `activity_rules` - // block — that collapse is covered below against a workflow that declares them.) + // The activity body itself is still delivered. (`work-package` declares an + // activity-scoped rules bucket alongside its orchestrator-scoped `rules.workflow`, so + // `start-work-package` carries an inherited `activity_rules` block; on a byte-identical + // refetch that block collapses to a marker just like the technique bundle above.) const secondBody = parse(second.bodyText) as Record; - expect(secondBody['activity_rules']).toBeUndefined(); + expect(isUnchangedMarker(secondBody['activity_rules'])).toBe(true); expect(secondBody['id']).toBe('start-work-package'); expect(secondBody['steps']).toBeDefined(); diff --git a/workflows b/workflows index f84fe02b1..bd9c03dfd 160000 --- a/workflows +++ b/workflows @@ -1 +1 @@ -Subproject commit f84fe02b12f9617f401767b9b96f329d8c13225c +Subproject commit bd9c03dfd549aa442047aae0488f7188338feb90