From 940d394d73374be776c15268db2125271f6b7e60 Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Mon, 20 Jul 2026 15:35:21 -0400 Subject: [PATCH 1/4] release: bump @workspacejson/codex-mcp to 0.1.7 Stages 0.1.7 across the three sync points (package.json, package-lock, runtime VERSION in src/index.ts). Cut from main so PR #10 (e0827a8, array frameworkManifest normalization) is included -- the prior release branch did not contain it. Normalizer fix only. Extension stays 0.1.2 (separate vsce track). --- package-lock.json | 4 ++-- package.json | 2 +- src/index.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5325fdd..32a53b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@workspacejson/codex-mcp", - "version": "0.1.6", + "version": "0.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@workspacejson/codex-mcp", - "version": "0.1.6", + "version": "0.1.7", "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.20.0", diff --git a/package.json b/package.json index 2b816ee..356d37b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@workspacejson/codex-mcp", - "version": "0.1.6", + "version": "0.1.7", "description": "MCP server that surfaces workspace.json fragility and co-change intelligence to OpenAI Codex before it edits code.", "license": "Apache-2.0", "type": "module", diff --git a/src/index.ts b/src/index.ts index 0a100e4..861f16d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { isVerifyEnabled } from "./config.js"; import { SERVER_INSTRUCTIONS } from "./constants.js"; import { registerWorkspaceTools } from "./tools/workspace.js"; -const VERSION = "0.1.6"; +const VERSION = "0.1.7"; function buildServer(): McpServer { const server = new McpServer( From c16539344ee4460743a090b518ec478734b0791f Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Mon, 20 Jul 2026 16:05:48 -0400 Subject: [PATCH 2/4] chore: gitignore local editor state .vscode/ is untracked developer convenience (extension-host launch + build task). It failed check:structure, the first gate in prepublishOnly, blocking local publish. The checker consults git check-ignore plus config/repository-structure.json; .vscodeignore governs vsce bundling only and has no bearing here. Kept separate from the 0.1.7 version bump. --- .gitignore | 4 ++++ biome.json | 1 + 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 8ded44e..1685541 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,10 @@ hac120-desktop.png # Local Claude state is machine-specific, not product source. .claude/ +# Local editor state. The extension-host launch config is a developer +# convenience, not a release input; keep it out of the published repo. +.vscode/ + # Local Codex transcript-processing scratchwork may contain prompts and tool traces. /scripts/extract-session*.mjs diff --git a/biome.json b/biome.json index 13096d8..d00857f 100644 --- a/biome.json +++ b/biome.json @@ -24,6 +24,7 @@ "ignore": [ ".claude", ".local", + ".vscode", "dist", "node_modules", "fixture", From 62bbddfc0954f44530119b05042bf97444850970 Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Mon, 20 Jul 2026 17:48:44 -0400 Subject: [PATCH 3/4] ci(publish): assert the normalizer fix is in the shipped tarball The npm publish workflow asserted tarball contents by filename only, so a release could ship without the array-manifest normalizer and stay green. 0.1.5 and 0.1.6 both did exactly that (HAC-206). Extracts the packed tarball and greps dist/services/workspace.js for normalizeFrameworkManifest. Verified to discriminate: the published 0.1.6 tarball fails this check, a 0.1.7 pack passes it. Scope is deliberately narrow -- this is a presence check on a symbol. It catches the fix going missing, not the fix being wrong. A behavioural gate needs a non-empty frameworkManifest fixture, because an empty array normalizes to undefined with or without the fix and discriminates nothing. That fixture is the follow-up. --- .github/workflows/publish-npm.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 083d9ff..0395471 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -52,6 +52,17 @@ jobs: test -f "$TAR" tar -tzf "$TAR" | grep -F 'package/.codex-plugin/assets/marketplace-icon.svg' tar -tzf "$TAR" | grep -F 'package/hooks/pre-edit-check.mjs' + # Assert the array-manifest normalizer is in the SHIPPED bytes, not + # merely in the branch. 0.1.5 and 0.1.6 both published without it + # while every branch check was green (HAC-206). Note this is a + # presence check on a symbol: it catches the fix going missing, not + # the fix being wrong. A behavioural gate needs a non-empty + # frameworkManifest fixture -- an empty array normalizes to + # undefined with or without the fix, so it discriminates nothing. + mkdir -p /tmp/workspacejson-release/inspect + tar -xzf "$TAR" -C /tmp/workspacejson-release/inspect + grep -q 'normalizeFrameworkManifest' \ + /tmp/workspacejson-release/inspect/package/dist/services/workspace.js - name: Install packed artifact in a clean project run: | mkdir -p /tmp/workspacejson-release/install From 92cfa9bab8d0e810197757e1177ac9557d933e51 Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Tue, 21 Jul 2026 18:58:05 -0400 Subject: [PATCH 4/4] release: 0.1.8 --- CHANGELOG.md | 8 ++++ README.md | 40 ++++++++++++++---- docs/fixture-verification.md | 76 +++++++++++++++++++++++------------ docs/submission/build-week.md | 2 +- package-lock.json | 4 +- package.json | 2 +- scripts/smoke.mjs | 15 +++++++ src/evidence.ts | 2 +- src/index.ts | 2 +- src/tools/workspace.ts | 2 +- tests/unit/evidence.test.ts | 21 ++++++++++ 11 files changed, 135 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 875ad77..0d3d829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.8] - 2026-07-21 + +### Fixed + +- Bounded the evidenced-partner deny guidance: include the recorded partners, or stop and review the exception with a human; no approval bypass is implied. +- Distinguished the local two-partner fixture walkthrough from Billfold's separate one-partner provider-demo proof path. +- Corrected public wording for the hero, generator producer, network boundary, path coverage limits, and Billfold reproduction links. + ## [0.1.5] - 2026-07-17 ### Fixed diff --git a/README.md b/README.md index dc724dd..482a0ff 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@
-

Portable repository history that changes Codex's plan before an evidenced risky edit lands.

+

Repository evidence that helps Codex plan around recorded risky changes.

@workspacejson/codex-mcp

@@ -25,9 +25,8 @@ | | | | --- | --- | | Task | Update the checkout route | -| Without `workspace.json` | Codex proposes one file | -| With `workspace.json` | The hook identifies two evidenced partners | -| Enforcement | The incomplete patch is denied | +| Recorded evidence | The route and its webhook partner share a co-change history — an incident and a revert, not an import | +| An incomplete patch | The hook denies it, citing the specific evidence and the omitted partner | | Outcome | Codex revises the changeset before the edit lands | ## Installation @@ -115,7 +114,7 @@ npx agents-audit@0.4.3 generate . This writes `.agents/workspace.json` with repository topology and hygiene. Today, `generated.fileIndex` is empty and `manual` fragility/co-change evidence is not auto-generated — those remain human-authored (ASSERTED tier at minimum, OBSERVED when backed by evidence records). The generator does not guess risk signals; guessed churn has no evidence records, remains ASSERTED, and cannot block. See [`fixture/`](fixture/) for a worked example with manual evidence. -### Verify in two minutes +### Local proof path — two recorded partners `generate` (above) writes repository topology only — no fragility or co-change evidence, so a freshly generated `workspace.json` has nothing to deny yet. To see the deny path itself, use this repo's `fixture/`, whose `manual` evidence is hand-authored for exactly this demo: @@ -125,12 +124,36 @@ This writes `.agents/workspace.json` with repository topology and hygiene. Today No configuration beyond step 1 above. On your own repo, the same deny path activates once you've authored `manual.fragileFiles` / `manual.coChangePatterns` yourself — see [`docs/workspace-contract.md`](docs/workspace-contract.md). +### Provider-demo proof path — Billfold's one recorded partner + +The judge-facing demo runs against [`workspace-json/billfold`](https://github.com/workspace-json/billfold), a small public payments service. This is a separate proof path from this repository's local `fixture/`: Billfold uses the single recorded pairing shown on camera, `src/routes/checkout.ts` and `src/webhooks/stripe.ts`; the local walkthrough above uses `src/auth/session.ts` and `src/lib/format.ts`. + +```bash +git clone https://github.com/workspace-json/billfold.git +cd billfold +git checkout 5e97f1dc9e6a41eb80d2d6eb80d5ef703cbe1cde # main as of 2026-07-20; no tag covers this pairing yet +npm install +npx @workspacejson/codex-mcp install --with-hook +``` + +1. Open `billfold` in Codex. Ask it to change the idempotency-key format in `src/routes/checkout.ts`. +2. The hook denies the patch, citing the recorded revert/incident and the omitted partner, `src/webhooks/stripe.ts`. +3. Ask Codex to include `src/webhooks/stripe.ts` and retry — the patch proceeds. That clears the recorded-partner check; it is not a correctness verdict on the change (see [Current limitations](#current-limitations)). + +This pins to the commit above because `billfold`'s `main` is mutable and the two existing tags (`fixture-v1`, `fixture-v2`) predate this pairing — clone and stay on `main` instead if you want the current state. + ## How it works MCP supplies context. A deterministic hook enforces evidenced omissions. An optional, direct read-only GPT-5.6 API review challenges a supplied completed diff and preserves its request/response receipt locally. The reviewer never controls the hook, and a `PASS` verdict is not a safety certification. +```bash +git diff | npx @workspacejson/codex-mcp review --diff-stdin +``` + +Requires `OPENAI_API_KEY` (or `OPENROUTER_API_KEY`) in the environment. Without one, it reports `UNAVAILABLE` and deterministic enforcement is unaffected. + Full derivation rules for evidence tiers (`ASSERTED`/`OBSERVED`/`VERIFIED`), the hook's fail-open behavior, and the GPT-5.6 reviewer's scope live in [`docs/how-it-works.md`](docs/how-it-works.md). ## Operational guarantees @@ -147,7 +170,9 @@ Each is checkable, not asserted: run `npm run verify` from a clean clone to repr ## Trust boundary -The MCP and deterministic hook run locally over stdio and do not upload repository contents. Initial package installation may contact npm. The optional `review` command sends only the diff you explicitly supply to a configured API provider: OpenAI (`OPENAI_API_KEY`) or OpenRouter (`OPENROUTER_API_KEY`). When both keys exist, set `WORKSPACEJSON_REVIEWER_PROVIDER` to `openai` or `openrouter`; an explicit `WORKSPACEJSON_REVIEWER_BASE_URL` also selects OpenRouter. It uses `store: false` with OpenAI and preserves a local request/response receipt that identifies the provider and model. Do not supply diffs containing secrets. +**Local, no network:** the MCP server, the deterministic hook, and the VS Code extension run over stdio and the local filesystem only. None of them upload repository contents or make network calls. + +**Network, by explicit action only:** `npx` package installation contacts npm. The optional `review` command sends only the diff you explicitly supply to a configured API provider: OpenAI (`OPENAI_API_KEY`) or OpenRouter (`OPENROUTER_API_KEY`). When both keys exist, set `WORKSPACEJSON_REVIEWER_PROVIDER` to `openai` or `openrouter`; an explicit `WORKSPACEJSON_REVIEWER_BASE_URL` also selects OpenRouter. It uses `store: false` with OpenAI and preserves a local request/response receipt that identifies the provider and model. Do not supply diffs containing secrets. ## Current limitations @@ -156,6 +181,7 @@ The MCP and deterministic hook run locally over stdio and do not upload reposito - Missing or malformed `workspace.json` fails open with an explicit unavailable warning. - Stale evidence is not treated as proof of current risk. - `fragile:false` means the file has no recorded fragility, not that it is verified safe. +- Including a recorded partner's path clears the omission check; it confirms path coverage, not that the partner's content is correct or sufficient. - This does not replace tests, review, or repository instructions. ## Learn more @@ -168,7 +194,7 @@ The MCP and deterministic hook run locally over stdio and do not upload reposito - [Verification](docs/verification.md) — what's been verified and how - [Build Week disclosure](docs/submission/build-week.md) — what was authored in-window - [Development](docs/development.md) — build, test, and smoke-suite commands -- [Clean-install audit](docs/clean-install-audit.md) · [Fixture verification](docs/fixture-verification.md) +- [Clean-install audit](docs/clean-install-audit.md) · [Fixture verification](docs/fixture-verification.md) · [`billfold`](https://github.com/workspace-json/billfold) — the public repo behind the demo video ## License diff --git a/docs/fixture-verification.md b/docs/fixture-verification.md index d82d474..2e41484 100644 --- a/docs/fixture-verification.md +++ b/docs/fixture-verification.md @@ -1,42 +1,68 @@ -# Checkout fixture verification +# Billfold fixture verification -This public fixture is a small, controlled checkout scenario for reproducing -the documented co-change behavior. It is not a production incident record. +Billfold is the public fixture for the `workspace.json` / Codex demonstration. It is a +small, controlled checkout scenario for reproducing the documented co-change behavior — +not a production incident record. -## Frozen fixture +## Repository -- Repository: `workspace-json/codex-demo-fixture`. -- Annotated tag and commit: [`fixture-v2`](https://github.com/workspace-json/codex-demo-fixture/tree/dc6f4d721affac96d517ca96cad8ccf8d9c15e3c) - → `dc6f4d721affac96d517ca96cad8ccf8d9c15e3c`. -- Artifact: [`.agents/workspace.json`](https://github.com/workspace-json/codex-demo-fixture/blob/dc6f4d721affac96d517ca96cad8ccf8d9c15e3c/.agents/workspace.json). -- Artifact Git blob: `a6807d3ad39aa3f3a1f3471c2ae1d4288f879149`. +- Repository: [`workspace-json/billfold`](https://github.com/workspace-json/billfold). +- Reference commit: [`5e97f1d`](https://github.com/workspace-json/billfold/tree/5e97f1dc9e6a41eb80d2d6eb80d5ef703cbe1cde) + → `5e97f1dc9e6a41eb80d2d6eb80d5ef703cbe1cde` (2026-07-20). +- No annotated tag currently covers this pairing. The two existing tags, + `fixture-v1` and `fixture-v2`, predate it and point to a different recorded + pairing (`checkout.ts` co-changing with `src/auth/session.ts` and + `src/lib/format.ts` — the same pairing this repo's own `fixture/` directory + reproduces locally). Do not cite `fixture-v1`/`fixture-v2` for the pairing + described below. +- Artifact: [`.agents/workspace.json`](https://github.com/workspace-json/billfold/blob/5e97f1dc9e6a41eb80d2d6eb80d5ef703cbe1cde/.agents/workspace.json). +- Artifact Git blob: `b86f453b716e1f03dc2cb93734e48ba20fdd9f55`. - Artifact SHA-256: - `5c97c81c8d6457e795c174d740026862512925cbf2efa9c66c1a18712285593d`. + `be4072f3e1937f970fab290ec96ba9eabe34827623a84176be816d13e64b0484`. -The primary edit path is `src/routes/checkout.ts`; its recorded co-change -partners are `src/auth/session.ts` and `src/lib/format.ts`. +This is the provider-demo proof path. It is separate from this repository's local +`fixture/` walkthrough, which records two checkout partners (`src/auth/session.ts` and +`src/lib/format.ts`); Billfold records the one checkout/Stripe partner below. + +The primary edit path is `src/routes/checkout.ts`; its recorded co-change partner is +`src/webhooks/stripe.ts`. The relationship is not visible through a direct import or +shared symbol — checkout builds a retry key that the webhook parses independently. +It is recorded as a co-change pattern plus a fragility reason citing a 2026-02-28 +rounding change to `checkout.ts` that was reverted two days later +(`985b0d4`, `05989dc`), both reproducible with the `git log`/`git show` commands +embedded in the artifact's own evidence entries. ## Reproduce ```sh -git clone https://github.com/workspace-json/codex-demo-fixture.git -cd codex-demo-fixture -git switch --detach fixture-v2 -npm test +git clone https://github.com/workspace-json/billfold.git +cd billfold +git checkout 5e97f1dc9e6a41eb80d2d6eb80d5ef703cbe1cde +npm install +npx @workspacejson/codex-mcp install --with-hook ``` -The route-only regression commit -`ca1f7ec8e124b4050deb5cd6d704bea0fe1dcee7` fails `npm test`. The corrective -co-change commit `a9729be1486dc199adc1f42371847217cba9d883` and `fixture-v2` -pass it. +Ask Codex to change the idempotency-key format in `src/routes/checkout.ts` alone. The +hook denies the patch (exit code 2), citing the recorded evidence and the omitted +partner, `src/webhooks/stripe.ts`. Including that partner in the same patch clears the +deny; this confirms the recorded path is present, not that the included change is +correct — see the README's [Current limitations](../README.md#current-limitations). + +`billfold`'s own `scripts/capture-red-evidence.mjs` still hardcodes a regression commit +(`ca1f7ec8e124b4050deb5cd6d704bea0fe1dcee7`) from the prior `session.ts`/`format.ts` +pairing, predating the `stripe.ts` evidence above. It has not been updated for this +pairing and should not be used to reproduce a red/green `npm test` pair for the +checkout/webhook relationship described here. ## Spec compatibility The plugin reads only `manual.fragileFiles`, `manual.coChangePatterns`, `generated.fileIndex`, and `generated.frameworkManifest` when present. -The frozen artifact is a documented validate-and-warn case: with -`@workspacejson/spec` v0.4.1, both `validate()` and `validateV4()` return -`false`. The consumer still normalizes the locked paths, returning the checkout -fragility record, both recorded partners, and an indexed primary path. This -does not turn the validation discrepancy into an approval or safety claim. +Checked against the currently published `@workspacejson/spec@0.4.3`: `validate()` +returns `true` for the reference artifact above; `validateV4()` and `validateLegacy()` +both return `false`. The consumer still normalizes the locked paths, returning the +checkout fragility record, the recorded partner, and an indexed primary path. This +does not turn the `validateV4()`/`validateLegacy()` result into an approval or safety +claim, and `validate() === true` is not a correctness claim about the recorded +evidence either — only that the document's shape matches the general schema. diff --git a/docs/submission/build-week.md b/docs/submission/build-week.md index fef662e..562c388 100644 --- a/docs/submission/build-week.md +++ b/docs/submission/build-week.md @@ -1,5 +1,5 @@ # For the OpenAI Build Week submission -> This project was built during OpenAI Build Week as a new Codex integration for the open `workspace.json` standard. The `workspace.json` standard and its generator (`@workspacejson/cli`) are pre-existing open-source work, used here as a dependency the same way any participant could depend on it. The Build Week contribution is `@workspacejson/codex-mcp`: the MCP server, its tool surface, deterministic hook, packaging, and optional direct GPT-5.6 read-only API reviewer, all authored or integrated in-window and contained in this repository. +> This project was built during OpenAI Build Week as a new Codex integration for the open `workspace.json` standard. The `workspace.json` standard and its generator (`agents-audit`) are pre-existing open-source work, used here as a dependency the same way any participant could depend on it. The Build Week contribution is `@workspacejson/codex-mcp`: the MCP server, its tool surface, deterministic hook, packaging, and optional direct GPT-5.6 read-only API reviewer, all authored or integrated in-window and contained in this repository. Codex accelerated implementation, regression-test generation, packaging validation, and adversarial review. Human decisions control the product boundary: deterministic evidence remains the enforcement plane; GPT-5.6 performs visible semantic risk review without write or enforcement authority; Q approves the fixture, claims, evidence tiers, design, narration, and submission. diff --git a/package-lock.json b/package-lock.json index 32a53b9..6167355 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@workspacejson/codex-mcp", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@workspacejson/codex-mcp", - "version": "0.1.7", + "version": "0.1.8", "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.20.0", diff --git a/package.json b/package.json index 356d37b..3fcdeaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@workspacejson/codex-mcp", - "version": "0.1.7", + "version": "0.1.8", "description": "MCP server that surfaces workspace.json fragility and co-change intelligence to OpenAI Codex before it edits code.", "license": "Apache-2.0", "type": "module", diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs index 87f6bb0..696d1c5 100644 --- a/scripts/smoke.mjs +++ b/scripts/smoke.mjs @@ -41,6 +41,14 @@ check( ]), names.join(","), ); +const assessTool = tools.find((tool) => tool.name === "workspace_assess_change"); +check( + "assessment tool describes the bounded human-review path", + assessTool?.description?.includes( + "Include the recorded co-change partners, or stop and review the exception with a human.", + ), + assessTool?.description, +); // ── Tier derivation through file context ── const r1 = await client.callTool({ name: "workspace_get_file_context", arguments: { path: "src/routes/checkout.ts" } }); @@ -102,6 +110,13 @@ check( "deny message names missing partners", /src\/auth\/session\.ts/.test(d1.structuredContent?.assessments?.[0]?.message ?? ""), ); +check( + "deny message uses bounded human-review wording, not an approval bypass", + /Include the recorded co-change partners, or stop and review the exception with a human\./.test( + d1.structuredContent?.assessments?.[0]?.message ?? "", + ) && !/get explicit human approval/i.test(d1.structuredContent?.assessments?.[0]?.message ?? ""), + d1.structuredContent?.assessments?.[0]?.message, +); const d2 = await client.callTool({ name: "workspace_assess_change", diff --git a/src/evidence.ts b/src/evidence.ts index ac88d23..347e847 100644 --- a/src/evidence.ts +++ b/src/evidence.ts @@ -163,7 +163,7 @@ export function decideEnforcement(input: { if (evidencedFragile && missingPartners.length > 0) { action = "deny"; - message = `BLOCK [tier ${input.tier}]: ${input.path} is fragile${input.reason ? ` (${input.reason})` : ""} and historically co-changes with ${missingPartners.join(", ")}, which this change omits.${cite} Include the co-change partners or get explicit human approval to proceed without them.`; + message = `BLOCK [tier ${input.tier}]: ${input.path} is fragile${input.reason ? ` (${input.reason})` : ""} and historically co-changes with ${missingPartners.join(", ")}, which this change omits.${cite} Include the recorded co-change partners, or stop and review the exception with a human.`; } else if (evidencedFragile) { action = "warn"; message = `CAUTION [tier ${input.tier}]: ${input.path} is fragile${input.reason ? ` (${input.reason})` : ""}.${cite} Prefer minimal, well-tested changes.${ diff --git a/src/index.ts b/src/index.ts index 861f16d..5ead151 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { isVerifyEnabled } from "./config.js"; import { SERVER_INSTRUCTIONS } from "./constants.js"; import { registerWorkspaceTools } from "./tools/workspace.js"; -const VERSION = "0.1.7"; +const VERSION = "0.1.8"; function buildServer(): McpServer { const server = new McpServer( diff --git a/src/tools/workspace.ts b/src/tools/workspace.ts index 3339ced..01e28cc 100644 --- a/src/tools/workspace.ts +++ b/src/tools/workspace.ts @@ -409,7 +409,7 @@ Returns JSON: description: `Evaluate a SET of file paths (a proposed change) against workspace.json fragility and co-change history, and return a mechanical enforcement decision. Decision semantics (derived, never model-emitted): - - "deny": an evidenced-fragile file is touched while its recorded co-change partners are absent from the changeset. Include the partners or get explicit human approval. + - "deny": an evidenced-fragile file is touched while its recorded co-change partners are absent from the changeset. Include the recorded co-change partners, or stop and review the exception with a human. - "warn": evidenced-fragile file touched (partners covered), or co-change partners missing on a non-evidenced file. - "annotate": fragility asserted without evidence. Context only. - "none": no recorded history. This is NOT a safety approval; this tool never certifies a change as safe. diff --git a/tests/unit/evidence.test.ts b/tests/unit/evidence.test.ts index 3b0e11c..4e6731a 100644 --- a/tests/unit/evidence.test.ts +++ b/tests/unit/evidence.test.ts @@ -125,9 +125,30 @@ describe("decideEnforcement", () => { expect(result.message).toContain("BLOCK"); expect(result.message).toContain("src/auth/session.ts"); expect(result.message).toContain("revert d4e5f6"); + expect(result.message).toContain( + "Include the recorded co-change partners, or stop and review the exception with a human.", + ); + expect(result.message).not.toContain("get explicit human approval"); expect(result.message).not.toContain("safe"); }); + it("uses path membership and has no override for an evidenced missing partner", () => { + const input = { + path: "src/routes/checkout.ts", + fragile: true, + tier: "OBSERVED" as const, + evidence: [{ claim: "revert d4e5f6" }], + coChangePartners: ["src/auth/session.ts"], + changesetPaths: ["src/routes/checkout.ts", "src/auth/session-helper.ts"], + override: true, + }; + + const result = decideEnforcement(input); + + expect(result.action).toBe("deny"); + expect(result.missingPartners).toEqual(["src/auth/session.ts"]); + }); + it("warns when evidenced fragility is touched but partners are covered", () => { const result = decideEnforcement({ path: "src/routes/checkout.ts",