@derwinjs/db 0.7.0 → 0.8.0
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/dist/budget-gate.d.ts +43 -0
- package/dist/budget-gate.d.ts.map +1 -0
- package/dist/budget-gate.js +110 -0
- package/dist/budget-gate.js.map +1 -0
- package/dist/classification-override-store.d.ts +24 -0
- package/dist/classification-override-store.d.ts.map +1 -0
- package/dist/classification-override-store.js +131 -0
- package/dist/classification-override-store.js.map +1 -0
- package/dist/freeze-window-evaluator.d.ts +62 -0
- package/dist/freeze-window-evaluator.d.ts.map +1 -0
- package/dist/freeze-window-evaluator.js +236 -0
- package/dist/freeze-window-evaluator.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/kill-switch-evaluator.d.ts +68 -0
- package/dist/kill-switch-evaluator.d.ts.map +1 -0
- package/dist/kill-switch-evaluator.js +389 -0
- package/dist/kill-switch-evaluator.js.map +1 -0
- package/dist/path-tier-resolver.d.ts +47 -0
- package/dist/path-tier-resolver.d.ts.map +1 -0
- package/dist/path-tier-resolver.js +177 -0
- package/dist/path-tier-resolver.js.map +1 -0
- package/dist/trust-threshold-config-store.d.ts +20 -0
- package/dist/trust-threshold-config-store.d.ts.map +1 -0
- package/dist/trust-threshold-config-store.js +88 -0
- package/dist/trust-threshold-config-store.js.map +1 -0
- package/package.json +3 -3
- package/prisma/migrations/20260507120000_sprint8_policy_governance/migration.sql +58 -0
- package/prisma/migrations/20260507120100_sprint8_phase2_thresholds_and_freeze/migration.sql +33 -0
- package/prisma/migrations/20260507120200_sprint8_phase3_budget_cap/migration.sql +21 -0
- package/prisma/migrations/20260507120300_sprint8_phase4_kill_switches/migration.sql +74 -0
- package/prisma/schema.prisma +183 -11
- package/prisma-client/edge.js +96 -19
- package/prisma-client/index-browser.js +93 -16
- package/prisma-client/index.d.ts +10997 -3426
- package/prisma-client/index.js +96 -19
- package/prisma-client/package.json +1 -1
- package/prisma-client/schema.prisma +183 -11
- package/prisma-client/wasm.js +93 -16
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createPrismaPathTierResolver — Prisma-backed implementation of the SDK
|
|
3
|
+
* PathTierResolver contract introduced by QAP-080.
|
|
4
|
+
*
|
|
5
|
+
* Sprint 8 (Policy Governance, Phase 1). Resolves a list of paths to the
|
|
6
|
+
* **strictest** RiskTier among all rules that match any of the supplied
|
|
7
|
+
* paths. "Strictest" follows the RiskTier ladder: NEVER > HIGH > MEDIUM >
|
|
8
|
+
* LOW. Falls back to the project's `defaultTier` when no rule matches; if
|
|
9
|
+
* the project itself doesn't exist, falls back to LOW (defense-in-depth
|
|
10
|
+
* against tenant enumeration — wrong-project lookups must not throw or
|
|
11
|
+
* leak whether a Project row exists).
|
|
12
|
+
*
|
|
13
|
+
* No external glob library — `matchGlob` below is a small in-house helper
|
|
14
|
+
* that handles the three glob features we need (`**`, `*`, literals). We
|
|
15
|
+
* deliberately avoid pulling `minimatch` into @derwinjs/db to keep the
|
|
16
|
+
* dependency surface small for Sprint 8; if the Sprint 9 policy editor
|
|
17
|
+
* needs feature parity (negation, brace expansion, `?`), revisit then.
|
|
18
|
+
*
|
|
19
|
+
* Tenant isolation: every read scopes by projectId. setRules wraps DELETE
|
|
20
|
+
* + bulk INSERT in an interactive transaction so partial failures don't
|
|
21
|
+
* leave a half-applied ruleset.
|
|
22
|
+
*/
|
|
23
|
+
import { PathTierResolverError, RiskTierValues, } from '@derwinjs/sdk';
|
|
24
|
+
// ─── Tier ordering ────────────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Strictness ladder. The resolver picks the maximum-strictness tier among
|
|
27
|
+
* all matching rules. NEVER is strictest (forbidden), LOW is loosest.
|
|
28
|
+
*
|
|
29
|
+
* NOTE: the SDK's RiskTier enum currently is `LOW | MEDIUM | HIGH | NEVER`.
|
|
30
|
+
* The Sprint 8 plan referenced `CRITICAL` in early drafts; we honor the
|
|
31
|
+
* existing schema enum and treat NEVER as the strictest tier (it means
|
|
32
|
+
* "auto-fix forbidden — must be human-only"). If a future migration
|
|
33
|
+
* renames NEVER to CRITICAL, update this ladder accordingly.
|
|
34
|
+
*/
|
|
35
|
+
const TIER_STRICTNESS = {
|
|
36
|
+
LOW: 0,
|
|
37
|
+
MEDIUM: 1,
|
|
38
|
+
HIGH: 2,
|
|
39
|
+
NEVER: 3,
|
|
40
|
+
};
|
|
41
|
+
function strictest(a, b) {
|
|
42
|
+
return TIER_STRICTNESS[a] >= TIER_STRICTNESS[b] ? a : b;
|
|
43
|
+
}
|
|
44
|
+
// ─── Glob matcher ─────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Minimal glob → path matcher.
|
|
47
|
+
*
|
|
48
|
+
* `**` — matches zero or more path segments (including `/`)
|
|
49
|
+
* `*` — matches zero or more chars within a single segment (no `/`)
|
|
50
|
+
* anything else — literal
|
|
51
|
+
*
|
|
52
|
+
* Implemented by translating the glob to a RegExp. Anchored at both ends.
|
|
53
|
+
* This is intentionally a small subset — we don't support brace expansion,
|
|
54
|
+
* negation, character classes, or `?`. Add them when a real consumer
|
|
55
|
+
* needs them; YAGNI applies in Sprint 8.
|
|
56
|
+
*
|
|
57
|
+
* Throws PathTierResolverError(`invalid_pattern`) on a glob that produces
|
|
58
|
+
* an unparseable RegExp (extremely rare with the supported subset, but
|
|
59
|
+
* the guard exists so the contract's error-code surface is honored).
|
|
60
|
+
*/
|
|
61
|
+
export function matchGlob(pattern, path) {
|
|
62
|
+
// Tokenize so we can distinguish `**` from two consecutive `*`.
|
|
63
|
+
let regexSrc = '';
|
|
64
|
+
let i = 0;
|
|
65
|
+
while (i < pattern.length) {
|
|
66
|
+
const ch = pattern.charAt(i);
|
|
67
|
+
if (ch === '*') {
|
|
68
|
+
if (pattern[i + 1] === '*') {
|
|
69
|
+
// `**` — match anything including `/`
|
|
70
|
+
regexSrc += '.*';
|
|
71
|
+
i += 2;
|
|
72
|
+
// Skip a trailing `/` in `**/` so the rule matches the empty
|
|
73
|
+
// prefix case (e.g., `**/foo.ts` should match `foo.ts`).
|
|
74
|
+
if (pattern[i] === '/') {
|
|
75
|
+
regexSrc += '/?';
|
|
76
|
+
i += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// single `*` — match within a segment
|
|
81
|
+
regexSrc += '[^/]*';
|
|
82
|
+
i += 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (/[.+?^${}()|[\]\\]/.test(ch)) {
|
|
86
|
+
regexSrc += '\\' + ch;
|
|
87
|
+
i += 1;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
regexSrc += ch;
|
|
91
|
+
i += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
let re;
|
|
95
|
+
try {
|
|
96
|
+
re = new RegExp('^' + regexSrc + '$');
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
throw new PathTierResolverError('invalid_pattern', `PathTierResolver: invalid glob pattern ${JSON.stringify(pattern)}`, err);
|
|
100
|
+
}
|
|
101
|
+
return re.test(path);
|
|
102
|
+
}
|
|
103
|
+
// ─── Validation ──────────────────────────────────────────────────────────
|
|
104
|
+
function validateRule(rule) {
|
|
105
|
+
if (typeof rule.pattern !== 'string' || rule.pattern.length === 0) {
|
|
106
|
+
throw new PathTierResolverError('invalid_pattern', 'PathTierResolver: rule.pattern must be a non-empty string');
|
|
107
|
+
}
|
|
108
|
+
if (!RiskTierValues.includes(rule.tier)) {
|
|
109
|
+
throw new PathTierResolverError('invalid_tier', `PathTierResolver: rule.tier must be one of ${RiskTierValues.join(', ')} (got ${rule.tier})`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ─── Factory ─────────────────────────────────────────────────────────────
|
|
113
|
+
export function createPrismaPathTierResolver(config) {
|
|
114
|
+
const { prisma } = config;
|
|
115
|
+
return {
|
|
116
|
+
async resolveTier(input) {
|
|
117
|
+
const { projectId, paths } = input;
|
|
118
|
+
// Read project row for the fallback tier. findUnique (not findFirst)
|
|
119
|
+
// because Project.id is the unique key. Wrong-project / unknown-
|
|
120
|
+
// project returns null → fall back to LOW (no leak that the project
|
|
121
|
+
// doesn't exist).
|
|
122
|
+
const project = await prisma.project.findUnique({
|
|
123
|
+
where: { id: projectId },
|
|
124
|
+
select: { defaultTier: true },
|
|
125
|
+
});
|
|
126
|
+
const fallback = project?.defaultTier ?? 'LOW';
|
|
127
|
+
if (paths.length === 0) {
|
|
128
|
+
return fallback;
|
|
129
|
+
}
|
|
130
|
+
const rules = await prisma.pathTierRule.findMany({
|
|
131
|
+
where: { projectId },
|
|
132
|
+
orderBy: { priority: 'asc' },
|
|
133
|
+
});
|
|
134
|
+
let strictestSeen = null;
|
|
135
|
+
for (const rule of rules) {
|
|
136
|
+
const matched = paths.some((p) => matchGlob(rule.pattern, p));
|
|
137
|
+
if (matched) {
|
|
138
|
+
strictestSeen = strictestSeen === null ? rule.tier : strictest(strictestSeen, rule.tier);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return strictestSeen ?? fallback;
|
|
142
|
+
},
|
|
143
|
+
async listRules(input) {
|
|
144
|
+
const rows = await prisma.pathTierRule.findMany({
|
|
145
|
+
where: { projectId: input.projectId },
|
|
146
|
+
orderBy: { priority: 'asc' },
|
|
147
|
+
});
|
|
148
|
+
return rows.map((row) => ({
|
|
149
|
+
pattern: row.pattern,
|
|
150
|
+
tier: row.tier,
|
|
151
|
+
...(row.reason !== null ? { reason: row.reason } : {}),
|
|
152
|
+
}));
|
|
153
|
+
},
|
|
154
|
+
async setRules(input) {
|
|
155
|
+
const { projectId, rules } = input;
|
|
156
|
+
// Validate up-front so a bad rule fails fast before we touch the DB.
|
|
157
|
+
for (const rule of rules)
|
|
158
|
+
validateRule(rule);
|
|
159
|
+
// Atomic replace: DELETE + bulk INSERT inside one transaction.
|
|
160
|
+
await prisma.$transaction(async (tx) => {
|
|
161
|
+
await tx.pathTierRule.deleteMany({ where: { projectId } });
|
|
162
|
+
if (rules.length === 0)
|
|
163
|
+
return;
|
|
164
|
+
await tx.pathTierRule.createMany({
|
|
165
|
+
data: rules.map((rule, idx) => ({
|
|
166
|
+
projectId,
|
|
167
|
+
pattern: rule.pattern,
|
|
168
|
+
tier: rule.tier,
|
|
169
|
+
priority: idx,
|
|
170
|
+
reason: rule.reason ?? null,
|
|
171
|
+
})),
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=path-tier-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-tier-resolver.js","sourceRoot":"","sources":["../src/path-tier-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createPrismaTrustThresholdConfigStore — Prisma-backed implementation of
|
|
3
|
+
* the SDK TrustThresholdConfigStore contract introduced by QAP-082.
|
|
4
|
+
*
|
|
5
|
+
* Sprint 8 Phase 2 (Policy Governance). The config is stored as a JSON
|
|
6
|
+
* column on Project.trustThresholds. Null in storage means "use defaults".
|
|
7
|
+
*
|
|
8
|
+
* Tenant isolation: every method scopes by projectId. Pattern D applies —
|
|
9
|
+
* unknown / wrong-project lookups return DEFAULT_TRUST_THRESHOLDS rather
|
|
10
|
+
* than throwing or leaking existence (defense-in-depth against tenant
|
|
11
|
+
* enumeration).
|
|
12
|
+
*/
|
|
13
|
+
import type { PrismaClient } from './prisma.js';
|
|
14
|
+
import { type TrustThresholdConfigStore } from '@derwinjs/sdk';
|
|
15
|
+
export interface PrismaTrustThresholdConfigStoreConfig {
|
|
16
|
+
/** Generated Prisma client. Pass an instance per process. */
|
|
17
|
+
prisma: PrismaClient;
|
|
18
|
+
}
|
|
19
|
+
export declare function createPrismaTrustThresholdConfigStore(config: PrismaTrustThresholdConfigStoreConfig): TrustThresholdConfigStore;
|
|
20
|
+
//# sourceMappingURL=trust-threshold-config-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-threshold-config-store.d.ts","sourceRoot":"","sources":["../src/trust-threshold-config-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,qCAAqC;IACpD,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;CACtB;AA8DD,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,qCAAqC,GAC5C,yBAAyB,CA4B3B"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createPrismaTrustThresholdConfigStore — Prisma-backed implementation of
|
|
3
|
+
* the SDK TrustThresholdConfigStore contract introduced by QAP-082.
|
|
4
|
+
*
|
|
5
|
+
* Sprint 8 Phase 2 (Policy Governance). The config is stored as a JSON
|
|
6
|
+
* column on Project.trustThresholds. Null in storage means "use defaults".
|
|
7
|
+
*
|
|
8
|
+
* Tenant isolation: every method scopes by projectId. Pattern D applies —
|
|
9
|
+
* unknown / wrong-project lookups return DEFAULT_TRUST_THRESHOLDS rather
|
|
10
|
+
* than throwing or leaking existence (defense-in-depth against tenant
|
|
11
|
+
* enumeration).
|
|
12
|
+
*/
|
|
13
|
+
import { DEFAULT_TRUST_THRESHOLDS, TrustThresholdConfigError, } from '@derwinjs/sdk';
|
|
14
|
+
// ─── Validation ──────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Each threshold must be a finite integer in [0, 100]. We accept floats
|
|
17
|
+
* because the JSON column can store them, but the dispatcher's comparison
|
|
18
|
+
* is integer-percent semantically; reject NaN / Infinity / out-of-range.
|
|
19
|
+
*/
|
|
20
|
+
function validateConfig(config) {
|
|
21
|
+
for (const [key, value] of [
|
|
22
|
+
['low', config.low],
|
|
23
|
+
['medium', config.medium],
|
|
24
|
+
['high', config.high],
|
|
25
|
+
]) {
|
|
26
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
27
|
+
throw new TrustThresholdConfigError('invalid_input', `TrustThresholdConfig: ${key} must be a finite number (got ${String(value)})`);
|
|
28
|
+
}
|
|
29
|
+
if (value < 0 || value > 100) {
|
|
30
|
+
throw new TrustThresholdConfigError('invalid_input', `TrustThresholdConfig: ${key} must be in [0, 100] (got ${String(value)})`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Best-effort coercion of an unknown JSON value into a TrustThresholdConfig.
|
|
36
|
+
* Returns null if the shape is wrong — caller falls back to defaults. We
|
|
37
|
+
* intentionally do not throw here: defense-in-depth so a corrupted row
|
|
38
|
+
* doesn't cascade into a runtime error.
|
|
39
|
+
*/
|
|
40
|
+
function tryParseConfig(value) {
|
|
41
|
+
if (value !== null &&
|
|
42
|
+
typeof value === 'object' &&
|
|
43
|
+
!Array.isArray(value) &&
|
|
44
|
+
'low' in value &&
|
|
45
|
+
'medium' in value &&
|
|
46
|
+
'high' in value) {
|
|
47
|
+
const v = value;
|
|
48
|
+
if (typeof v.low === 'number' &&
|
|
49
|
+
typeof v.medium === 'number' &&
|
|
50
|
+
typeof v.high === 'number' &&
|
|
51
|
+
Number.isFinite(v.low) &&
|
|
52
|
+
Number.isFinite(v.medium) &&
|
|
53
|
+
Number.isFinite(v.high)) {
|
|
54
|
+
return { low: v.low, medium: v.medium, high: v.high };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
// ─── Factory ─────────────────────────────────────────────────────────────
|
|
60
|
+
export function createPrismaTrustThresholdConfigStore(config) {
|
|
61
|
+
const { prisma } = config;
|
|
62
|
+
return {
|
|
63
|
+
async getConfig(input) {
|
|
64
|
+
const project = await prisma.project.findUnique({
|
|
65
|
+
where: { id: input.projectId },
|
|
66
|
+
select: { trustThresholds: true },
|
|
67
|
+
});
|
|
68
|
+
if (project === null)
|
|
69
|
+
return { ...DEFAULT_TRUST_THRESHOLDS };
|
|
70
|
+
const parsed = tryParseConfig(project.trustThresholds);
|
|
71
|
+
return parsed ?? { ...DEFAULT_TRUST_THRESHOLDS };
|
|
72
|
+
},
|
|
73
|
+
async setConfig(input) {
|
|
74
|
+
validateConfig(input.config);
|
|
75
|
+
await prisma.project.update({
|
|
76
|
+
where: { id: input.projectId },
|
|
77
|
+
data: {
|
|
78
|
+
trustThresholds: {
|
|
79
|
+
low: input.config.low,
|
|
80
|
+
medium: input.config.medium,
|
|
81
|
+
high: input.config.high,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=trust-threshold-config-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-threshold-config-store.js","sourceRoot":"","sources":["../src/trust-threshold-config-store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@derwinjs/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Prisma schema + migrations for Derwin's own Postgres. 14 models, project-namespaced. Per ADR-0005. Ships its own generated Prisma client (multi-platform binaries) for cross-consumer compatibility.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@prisma/client": "^5.22.0",
|
|
36
|
-
"@derwinjs/core": "0.
|
|
37
|
-
"@derwinjs/sdk": "0.
|
|
36
|
+
"@derwinjs/core": "0.8.0",
|
|
37
|
+
"@derwinjs/sdk": "0.8.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@vitest/coverage-v8": "^2.1.9",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
-- Sprint 8 (QAP-080..081) — Policy governance: path-tier rules + classification override table.
|
|
2
|
+
--
|
|
3
|
+
-- Promotes Policy.pathRules / Policy.classificationOverrides JSON columns to
|
|
4
|
+
-- first-class tables so the policy-editor UI (Sprint 9 / QAP-088) can render,
|
|
5
|
+
-- reorder, and audit them as discrete rows. The legacy JSON fields on Policy
|
|
6
|
+
-- stay for back-compat with QAP-013's createPrismaFixPolicy until Sprint 9
|
|
7
|
+
-- migrates the read path.
|
|
8
|
+
--
|
|
9
|
+
-- Idempotent (IF NOT EXISTS) — safe to re-run on environments where the
|
|
10
|
+
-- enum or tables already exist.
|
|
11
|
+
|
|
12
|
+
-- 1. Project.defaultTier — fallback tier when no PathTierRule matches.
|
|
13
|
+
ALTER TABLE "derwin"."projects"
|
|
14
|
+
ADD COLUMN IF NOT EXISTS "defaultTier" "derwin"."RiskTier" NOT NULL DEFAULT 'LOW';
|
|
15
|
+
|
|
16
|
+
-- 2. OverrideMode enum.
|
|
17
|
+
DO $$ BEGIN
|
|
18
|
+
CREATE TYPE "derwin"."OverrideMode" AS ENUM ('BLOCK_AUTO_FIX', 'FORCE_ESCALATION', 'DOWNGRADE_TIER');
|
|
19
|
+
EXCEPTION
|
|
20
|
+
WHEN duplicate_object THEN NULL;
|
|
21
|
+
END $$;
|
|
22
|
+
|
|
23
|
+
-- 3. PathTierRule.
|
|
24
|
+
CREATE TABLE IF NOT EXISTS "derwin"."path_tier_rules" (
|
|
25
|
+
"id" TEXT PRIMARY KEY,
|
|
26
|
+
"projectId" TEXT NOT NULL,
|
|
27
|
+
"pattern" TEXT NOT NULL,
|
|
28
|
+
"tier" "derwin"."RiskTier" NOT NULL,
|
|
29
|
+
"priority" INTEGER NOT NULL,
|
|
30
|
+
"reason" TEXT,
|
|
31
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
32
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
33
|
+
CONSTRAINT "path_tier_rules_projectId_fkey"
|
|
34
|
+
FOREIGN KEY ("projectId") REFERENCES "derwin"."projects"("id")
|
|
35
|
+
ON DELETE CASCADE ON UPDATE CASCADE
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE INDEX IF NOT EXISTS "path_tier_rules_projectId_priority_idx"
|
|
39
|
+
ON "derwin"."path_tier_rules" ("projectId", "priority");
|
|
40
|
+
|
|
41
|
+
-- 4. ClassificationOverride.
|
|
42
|
+
CREATE TABLE IF NOT EXISTS "derwin"."classification_overrides" (
|
|
43
|
+
"id" TEXT PRIMARY KEY,
|
|
44
|
+
"projectId" TEXT NOT NULL,
|
|
45
|
+
"classification" TEXT NOT NULL,
|
|
46
|
+
"surface" TEXT,
|
|
47
|
+
"overrideMode" "derwin"."OverrideMode" NOT NULL,
|
|
48
|
+
"downgradeTier" "derwin"."RiskTier",
|
|
49
|
+
"reason" TEXT NOT NULL,
|
|
50
|
+
"createdBy" TEXT NOT NULL,
|
|
51
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
52
|
+
CONSTRAINT "classification_overrides_projectId_fkey"
|
|
53
|
+
FOREIGN KEY ("projectId") REFERENCES "derwin"."projects"("id")
|
|
54
|
+
ON DELETE CASCADE ON UPDATE CASCADE
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE INDEX IF NOT EXISTS "classification_overrides_projectId_classification_surface_idx"
|
|
58
|
+
ON "derwin"."classification_overrides" ("projectId", "classification", "surface");
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Sprint 8 Phase 2 (QAP-082..083) — Trust threshold config + freeze windows.
|
|
2
|
+
--
|
|
3
|
+
-- Phase 1 (20260507120000_sprint8_policy_governance) added PathTierRule,
|
|
4
|
+
-- ClassificationOverride, and Project.defaultTier. Phase 2 layers on:
|
|
5
|
+
-- 1. Project.trustThresholds JSONB — per-tier minimum trustPercent for
|
|
6
|
+
-- auto-fix dispatch. Null → use SDK defaults ({low:60, medium:80, high:95}).
|
|
7
|
+
-- 2. freeze_windows table — operator-defined recurring dispatch freeze
|
|
8
|
+
-- windows. Cron-driven, 5-field, no seconds.
|
|
9
|
+
--
|
|
10
|
+
-- Idempotent (IF NOT EXISTS). Safe to re-run on environments where Phase 1
|
|
11
|
+
-- already landed but Phase 2 didn't.
|
|
12
|
+
|
|
13
|
+
-- 1. Project.trustThresholds — JSONB, nullable.
|
|
14
|
+
ALTER TABLE "derwin"."projects"
|
|
15
|
+
ADD COLUMN IF NOT EXISTS "trustThresholds" JSONB;
|
|
16
|
+
|
|
17
|
+
-- 2. FreezeWindow.
|
|
18
|
+
CREATE TABLE IF NOT EXISTS "derwin"."freeze_windows" (
|
|
19
|
+
"id" TEXT PRIMARY KEY,
|
|
20
|
+
"projectId" TEXT NOT NULL,
|
|
21
|
+
"cronExpression" TEXT NOT NULL,
|
|
22
|
+
"durationMinutes" INTEGER NOT NULL,
|
|
23
|
+
"label" TEXT NOT NULL,
|
|
24
|
+
"blocksDispatch" BOOLEAN NOT NULL DEFAULT TRUE,
|
|
25
|
+
"enabled" BOOLEAN NOT NULL DEFAULT TRUE,
|
|
26
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
27
|
+
CONSTRAINT "freeze_windows_projectId_fkey"
|
|
28
|
+
FOREIGN KEY ("projectId") REFERENCES "derwin"."projects"("id")
|
|
29
|
+
ON DELETE CASCADE ON UPDATE CASCADE
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE INDEX IF NOT EXISTS "freeze_windows_projectId_enabled_idx"
|
|
33
|
+
ON "derwin"."freeze_windows" ("projectId", "enabled");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- Sprint 8 Phase 3 (QAP-084) — Per-project budget cap + soft-warn pct.
|
|
2
|
+
--
|
|
3
|
+
-- Adds the budget-gate columns the BudgetGate contract reads:
|
|
4
|
+
-- 1. Project.monthlyBudgetUsd — DOUBLE PRECISION, nullable. Null = NO_CAP.
|
|
5
|
+
-- 2. Project.budgetSoftWarnPct — DOUBLE PRECISION, default 0.8 (80%).
|
|
6
|
+
--
|
|
7
|
+
-- The dispatcher consults a BudgetGate before runFixCycle and refuses new
|
|
8
|
+
-- auto-fix dispatches when month-to-date spend / monthlyBudgetUsd >= 1.0.
|
|
9
|
+
-- Soft-warn (>= budgetSoftWarnPct, < 1.0) is observable but non-blocking;
|
|
10
|
+
-- hard-stop (>= 1.0) blocks. NO_CAP (monthlyBudgetUsd IS NULL) is the
|
|
11
|
+
-- pre-Phase-3 default and preserves legacy behavior for projects that have
|
|
12
|
+
-- not opted into explicit USD caps.
|
|
13
|
+
--
|
|
14
|
+
-- Idempotent (IF NOT EXISTS). Safe to re-run on environments where Phase 1
|
|
15
|
+
-- and Phase 2 have already landed but Phase 3 has not.
|
|
16
|
+
|
|
17
|
+
ALTER TABLE "derwin"."projects"
|
|
18
|
+
ADD COLUMN IF NOT EXISTS "monthlyBudgetUsd" DOUBLE PRECISION;
|
|
19
|
+
|
|
20
|
+
ALTER TABLE "derwin"."projects"
|
|
21
|
+
ADD COLUMN IF NOT EXISTS "budgetSoftWarnPct" DOUBLE PRECISION NOT NULL DEFAULT 0.8;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
-- Sprint 8 Phase 4 (QAP-085 / QAP-086 / QAP-087) — Three kill switches.
|
|
2
|
+
--
|
|
3
|
+
-- Adds the schema the KillSwitchEvaluator contract reads/writes:
|
|
4
|
+
-- 1. Project.killSwitchConfig — JSONB, nullable. Per-project threshold
|
|
5
|
+
-- overrides; null → use SDK defaults.
|
|
6
|
+
-- 2. KillSwitchType enum — three trigger discriminators.
|
|
7
|
+
-- 3. KillSwitchScope enum — three granularity discriminators.
|
|
8
|
+
-- 4. kill_switch_states table — history-preserving log of every trip.
|
|
9
|
+
--
|
|
10
|
+
-- The dispatcher consults a KillSwitchEvaluator before runFixCycle and
|
|
11
|
+
-- refuses to dispatch when any matching engaged switch is present:
|
|
12
|
+
-- - PROJECT or PLATFORM-scope switches block before ticket fetch.
|
|
13
|
+
-- - CLASSIFICATION-scope switches block after ticket fetch when the
|
|
14
|
+
-- ticket's classification + surface match the engaged switch's tuple.
|
|
15
|
+
--
|
|
16
|
+
-- History semantics: a new trip inserts a new row; an unblock flips
|
|
17
|
+
-- engaged → false on the existing row; subsequent re-trips on the same
|
|
18
|
+
-- tuple insert another row. This preserves the audit trail.
|
|
19
|
+
--
|
|
20
|
+
-- Idempotent (IF NOT EXISTS / DO ... EXCEPTION). Safe to re-run on
|
|
21
|
+
-- environments where Phase 1 / Phase 2 / Phase 3 already landed but
|
|
22
|
+
-- Phase 4 has not.
|
|
23
|
+
|
|
24
|
+
-- 1. Project.killSwitchConfig — JSONB, nullable.
|
|
25
|
+
ALTER TABLE "derwin"."projects"
|
|
26
|
+
ADD COLUMN IF NOT EXISTS "killSwitchConfig" JSONB;
|
|
27
|
+
|
|
28
|
+
-- 2. KillSwitchType enum.
|
|
29
|
+
DO $$ BEGIN
|
|
30
|
+
CREATE TYPE "derwin"."KillSwitchType" AS ENUM (
|
|
31
|
+
'SUCCESS_RATE_DROP',
|
|
32
|
+
'CONSECUTIVE_FAILURE',
|
|
33
|
+
'REGRESSION_CASCADE'
|
|
34
|
+
);
|
|
35
|
+
EXCEPTION
|
|
36
|
+
WHEN duplicate_object THEN NULL;
|
|
37
|
+
END $$;
|
|
38
|
+
|
|
39
|
+
-- 3. KillSwitchScope enum.
|
|
40
|
+
DO $$ BEGIN
|
|
41
|
+
CREATE TYPE "derwin"."KillSwitchScope" AS ENUM (
|
|
42
|
+
'CLASSIFICATION',
|
|
43
|
+
'PROJECT',
|
|
44
|
+
'PLATFORM'
|
|
45
|
+
);
|
|
46
|
+
EXCEPTION
|
|
47
|
+
WHEN duplicate_object THEN NULL;
|
|
48
|
+
END $$;
|
|
49
|
+
|
|
50
|
+
-- 4. KillSwitchState table.
|
|
51
|
+
CREATE TABLE IF NOT EXISTS "derwin"."kill_switch_states" (
|
|
52
|
+
"id" TEXT PRIMARY KEY,
|
|
53
|
+
"projectId" TEXT,
|
|
54
|
+
"classification" TEXT,
|
|
55
|
+
"surface" TEXT,
|
|
56
|
+
"type" "derwin"."KillSwitchType" NOT NULL,
|
|
57
|
+
"scope" "derwin"."KillSwitchScope" NOT NULL,
|
|
58
|
+
"engaged" BOOLEAN NOT NULL DEFAULT TRUE,
|
|
59
|
+
"engagedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
60
|
+
"unblockedAt" TIMESTAMP(3),
|
|
61
|
+
"unblockedBy" TEXT,
|
|
62
|
+
"reason" TEXT NOT NULL,
|
|
63
|
+
"triggerMetrics" JSONB NOT NULL,
|
|
64
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
65
|
+
CONSTRAINT "kill_switch_states_projectId_fkey"
|
|
66
|
+
FOREIGN KEY ("projectId") REFERENCES "derwin"."projects"("id")
|
|
67
|
+
ON DELETE CASCADE ON UPDATE CASCADE
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE INDEX IF NOT EXISTS "kill_switch_states_projectId_engaged_idx"
|
|
71
|
+
ON "derwin"."kill_switch_states" ("projectId", "engaged");
|
|
72
|
+
|
|
73
|
+
CREATE INDEX IF NOT EXISTS "kill_switch_states_type_scope_engaged_idx"
|
|
74
|
+
ON "derwin"."kill_switch_states" ("type", "scope", "engaged");
|