Skip to content

Commit cfd142e

Browse files
clay-goodclaude
andcommitted
fix(rules): DPA-049 detect 'be responsible for' / 'pay' / passive 'borne by Controller' audit-cost allocation (v1.1.0)
v1.0.0 required the exact verb "bear" ("controller shall bear all costs of any audit") or "at controller's expense", and missed the common variants: the active verbs "shall be responsible for" / "shall pay" / "shall cover" all audit costs, and the passive voice "all costs of any audit shall be borne by the Controller". Add an active-verb pattern and a passive pattern, both still requiring an all-encompassing quantifier (all / entire / full / any) so a partial-cost split is not flagged. The existing except/unless carve-out guards are unchanged. Probe: 5 fire cases (2 baseline + 3 previously-missed), 4 silent cases (except-carve-out, each-party-own-cost, shared-equally, reversed processor- pays-on-breach). Finding-delta over the contracts corpus and v3/v4 goldens: zero fired-flips, zero finding-count changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 35337a7 commit cfd142e

342 files changed

Lines changed: 380 additions & 340 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/engine/rules/v3/dpa-gdpr/dpa-gdpr-ruleset.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,3 +593,33 @@ describe("DPA-023 — hand-waving security recognizes bare 'reasonable/adequate
593593
expect(await fires(b)).toBe(false);
594594
});
595595
});
596+
597+
describe("DPA-049 — audit-cost allocation recognizes 'be responsible for' / 'pay' / passive 'borne by' forms (v1.1.0)", () => {
598+
const fires = async (b: string) =>
599+
(
600+
await runEngine({
601+
rules: DPA_GDPR_RULES,
602+
ctx: withDpa(buildContext(["DPA", b])),
603+
source_file: SRC,
604+
})
605+
).findings.some((f) => f.rule_id === "DPA-049");
606+
607+
it.each([
608+
"Controller shall bear all costs of any audit.",
609+
"Any audit shall be at the Controller's sole expense.",
610+
"Controller shall be responsible for all costs of the audit.",
611+
"All costs and expenses of any audit shall be borne by the Controller.",
612+
"The Controller shall pay all costs associated with any audit or inspection.",
613+
])("fires on all-audit-cost-on-controller: %s", async (b) => {
614+
expect(await fires(b)).toBe(true);
615+
});
616+
617+
it.each([
618+
"Controller shall bear the costs of a routine audit, except where the audit reveals a material breach, in which case Processor shall bear the cost.",
619+
"Each party shall bear its own costs of any audit.",
620+
"The cost of the audit shall be shared equally between the parties.",
621+
"Processor shall bear the cost of any audit that reveals a material breach.",
622+
])("stays silent on a carve-out / split / reversed allocation: %s", async (b) => {
623+
expect(await fires(b)).toBe(false);
624+
});
625+
});

src/engine/rules/v3/dpa-gdpr/rules.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,7 @@ export const DPA_GDPR_RULES: Rule[] = [
984984
}),
985985
language({
986986
id: "DPA-049",
987+
version: "1.1.0",
987988
name: "Processor caps audit cost on controller exclusively",
988989
description:
989990
"Flags clauses where the controller must bear the entire cost of any audit, including audits triggered by processor breach.",
@@ -995,9 +996,18 @@ export const DPA_GDPR_RULES: Rule[] = [
995996
"Standard practice splits routine-audit cost to the controller but allocates cost to the processor where the audit uncovers material breach.",
996997
recommendation:
997998
"Carve out an exception for audits revealing material breach (cost shifts to the processor).",
999+
// v1.0.0 required the exact verb "bear" ("controller shall bear all costs")
1000+
// or "at controller's expense". It missed the equally-common active verb
1001+
// variants ("controller shall be responsible for / shall pay all costs of
1002+
// the audit") and the passive voice ("all costs of any audit shall be borne
1003+
// by the Controller"). Add an active-verb pattern (bear / pay / cover /
1004+
// be responsible|liable for) and a passive pattern, both still requiring an
1005+
// all-encompassing quantifier so a partial-cost split is not flagged.
9981006
bad_patterns: [
9991007
/controller\s+(shall|will)\s+bear\s+(all|the\s+entire|the\s+full)\s+costs?\s+of\s+(any\s+)?audit/i,
10001008
/audit.{0,80}(at\s+(the\s+)?controller'?s?\s+(sole\s+)?(cost|expense))/is,
1009+
/(?:the\s+)?controller\s+(?:shall|will|must|agrees?\s+to)\s+(?:bear|pay|cover|be\s+(?:responsible|liable)\s+for)\s+(?:all|the\s+(?:entire|full)|any)\s+costs?(?:\s+and\s+expenses?)?[^.]{0,40}audit/is,
1010+
/(?:all\s+)?costs?(?:\s+and\s+expenses?)?\s+of\s+(?:any\s+|the\s+)*audit[^.]{0,60}(?:borne|paid)\s+(?:solely\s+|exclusively\s+)?by\s+(?:the\s+)?controller/is,
10011011
],
10021012
// The finding claims costs are allocated "without exception", but the
10031013
// patterns never look for one. A clause carrying the exact carve-out this

tests/golden/v3/expected/ai-addendum-minimal-pass.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/golden/v3/expected/ai-addendum-missing-ai-definitions-fail.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/golden/v3/expected/ai-addendum-missing-fine-tuning-deletion-fail.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/golden/v3/expected/ai-addendum-missing-hallucination-and-human-review-fail.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/golden/v3/expected/ai-addendum-missing-output-ownership-fail.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/golden/v3/expected/ai-addendum-missing-subprocessor-disclosure-fail.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/golden/v3/expected/ai-addendum-missing-transparency-disclosures-fail.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/golden/v3/expected/ai-addendum-training-without-optin-fail.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)