@brainervirus/workit-core 6.0.0 → 7.0.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/README.md +16 -34
- package/package.json +1 -1
- package/skills/workit-deslop/SKILL.md +1 -1
- package/skills/workit-implement/SKILL.md +9 -4
- package/skills/workit-review/SKILL.md +2 -0
- package/skills/workit-shape/SKILL.md +1 -1
- package/skills/workit-ship/SKILL.md +1 -1
- package/src/autonomy.ts +24 -0
- package/src/core/methods.ts +7 -5
- package/src/core/operation-input.ts +506 -0
- package/src/core/policy/derive.ts +338 -0
- package/src/core/policy/judgment.ts +192 -0
- package/src/core/policy-resolver.ts +32 -427
- package/src/core/task-contract.ts +40 -229
- package/src/core/task-engine.ts +51 -58
- package/src/core/task-evaluation.ts +35 -1
- package/src/core/workspaces.ts +2 -0
- package/src/core.ts +8 -6
- package/src/hooks/handle.ts +19 -4
- package/src/hooks/hosts/claude-code.ts +20 -1
- package/src/hooks/hosts/codex.ts +3 -0
- package/src/hooks/hosts/cursor.ts +11 -2
- package/src/hooks/hosts/fields.ts +35 -0
- package/src/hooks/hosts/opencode.ts +1 -0
- package/src/hooks/hosts/pi.ts +1 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/protocol.ts +3 -0
- package/src/hooks/write-gate.ts +303 -0
- package/src/ledger.ts +20 -0
|
@@ -1,462 +1,67 @@
|
|
|
1
|
-
import * as z from "zod";
|
|
2
1
|
import {
|
|
3
|
-
assessmentSchema,
|
|
4
2
|
canonicalJson,
|
|
5
|
-
constraintSchema,
|
|
6
|
-
decisionSchema,
|
|
7
3
|
failure,
|
|
8
|
-
findingSchema,
|
|
9
|
-
intentSchema,
|
|
10
4
|
POLICY_VERSION,
|
|
11
5
|
policyChangeSchema,
|
|
12
6
|
policySchema,
|
|
13
|
-
requirementId,
|
|
14
|
-
requirementSchema,
|
|
15
7
|
sha256,
|
|
16
8
|
success,
|
|
17
|
-
type Assessment,
|
|
18
9
|
type Constraint,
|
|
19
|
-
type Decision,
|
|
20
|
-
type Dimension,
|
|
21
|
-
type Finding,
|
|
22
10
|
type Intent,
|
|
11
|
+
type Judgment,
|
|
23
12
|
type Policy,
|
|
24
13
|
type PolicyChange,
|
|
25
|
-
type Requirement,
|
|
26
14
|
type Result,
|
|
27
|
-
type Scope,
|
|
28
15
|
} from "./task-contract";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
decisions: Decision[];
|
|
32
|
-
findings: Finding[];
|
|
33
|
-
requirements: Requirement[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type ResolverPreferences = {
|
|
37
|
-
fast?: boolean;
|
|
38
|
-
thorough?: boolean;
|
|
39
|
-
};
|
|
16
|
+
import { deriveRequirements } from "./policy/derive";
|
|
17
|
+
import type { VerificationMode } from "../autonomy";
|
|
40
18
|
|
|
41
19
|
export type ResolverInput = {
|
|
42
20
|
intent: Intent;
|
|
43
|
-
|
|
21
|
+
judgment: Judgment;
|
|
44
22
|
constraints: Constraint[];
|
|
45
|
-
|
|
46
|
-
preferences?: ResolverPreferences;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
type NormalizedResolverInput = ResolverInput & { policyVersion: typeof POLICY_VERSION };
|
|
50
|
-
|
|
51
|
-
const resolverInputSchema = z
|
|
52
|
-
.object({
|
|
53
|
-
intent: intentSchema,
|
|
54
|
-
assessment: assessmentSchema,
|
|
55
|
-
constraints: z.array(constraintSchema),
|
|
56
|
-
prior: z
|
|
57
|
-
.object({
|
|
58
|
-
decisions: z.array(decisionSchema),
|
|
59
|
-
findings: z.array(findingSchema),
|
|
60
|
-
requirements: z.array(requirementSchema),
|
|
61
|
-
})
|
|
62
|
-
.strict(),
|
|
63
|
-
preferences: z
|
|
64
|
-
.object({ fast: z.boolean().optional(), thorough: z.boolean().optional() })
|
|
65
|
-
.strict()
|
|
66
|
-
.optional(),
|
|
67
|
-
})
|
|
68
|
-
.strict();
|
|
69
|
-
|
|
70
|
-
const compareCodeUnits = (left: string, right: string): number => {
|
|
71
|
-
const length = Math.min(left.length, right.length);
|
|
72
|
-
for (let index = 0; index < length; index += 1) {
|
|
73
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
74
|
-
if (difference !== 0) return difference;
|
|
75
|
-
}
|
|
76
|
-
return left.length - right.length;
|
|
23
|
+
verification?: VerificationMode;
|
|
77
24
|
};
|
|
78
25
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const stableList = <T>(values: T[]): T[] =>
|
|
83
|
-
[...values].toSorted((left, right) =>
|
|
84
|
-
compareCodeUnits(canonicalJson(left), canonicalJson(right)),
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const normalizedScope = (scope: Scope): Scope => ({
|
|
88
|
-
description: scope.description,
|
|
89
|
-
paths: stableUnique(scope.paths),
|
|
90
|
-
exclusions: stableUnique(scope.exclusions),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const normalizedRefList = <T>(values: T[]): T[] => stableList(values);
|
|
94
|
-
|
|
95
|
-
const normalizeAssessment = (assessment: Assessment): Assessment => ({
|
|
96
|
-
facts: stableList(
|
|
97
|
-
assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) })),
|
|
98
|
-
),
|
|
99
|
-
signals: Object.fromEntries(
|
|
100
|
-
Object.entries(assessment.signals).map(([name, signal]) => [
|
|
101
|
-
name,
|
|
102
|
-
{ ...signal, refs: normalizedRefList(signal.refs) },
|
|
103
|
-
]),
|
|
104
|
-
) as Assessment["signals"],
|
|
105
|
-
consequences: stableList(
|
|
106
|
-
assessment.consequences.map((consequence) => ({
|
|
107
|
-
...consequence,
|
|
108
|
-
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) },
|
|
109
|
-
})),
|
|
110
|
-
),
|
|
111
|
-
verification: stableList(
|
|
112
|
-
assessment.verification.map((verification) => ({
|
|
113
|
-
...verification,
|
|
114
|
-
scope: normalizedScope(verification.scope),
|
|
115
|
-
availableChecks: normalizedRefList(verification.availableChecks),
|
|
116
|
-
gaps: stableUnique(verification.gaps),
|
|
117
|
-
})),
|
|
118
|
-
),
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const normalizeConstraint = (constraint: Constraint): Constraint => ({
|
|
122
|
-
...constraint,
|
|
123
|
-
source: constraint.source,
|
|
124
|
-
requires: stableList(
|
|
125
|
-
constraint.requires.map((requirement) => ({
|
|
126
|
-
...requirement,
|
|
127
|
-
scope: normalizedScope(requirement.scope),
|
|
128
|
-
refs: normalizedRefList(requirement.refs),
|
|
129
|
-
})),
|
|
130
|
-
),
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const normalizeDecision = (decision: Decision): Decision => ({
|
|
134
|
-
...decision,
|
|
135
|
-
binding: {
|
|
136
|
-
...decision.binding,
|
|
137
|
-
scope: normalizedScope(decision.binding.scope),
|
|
138
|
-
contentRefs: normalizedRefList(decision.binding.contentRefs),
|
|
139
|
-
},
|
|
140
|
-
requirementIds: stableUnique(decision.requirementIds),
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const normalizeIntent = (intent: Intent): Intent => ({
|
|
144
|
-
...intent,
|
|
145
|
-
scope: normalizedScope(intent.scope),
|
|
146
|
-
authorityRefs: normalizedRefList(intent.authorityRefs),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const normalizePrior = (prior: ResolverPrior): ResolverPrior => ({
|
|
150
|
-
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
151
|
-
findings: stableList(
|
|
152
|
-
prior.findings.map((finding) => ({
|
|
153
|
-
...finding,
|
|
154
|
-
scope: normalizedScope(finding.scope),
|
|
155
|
-
refs: normalizedRefList(finding.refs),
|
|
156
|
-
resolution: finding.resolution
|
|
157
|
-
? {
|
|
158
|
-
...finding.resolution,
|
|
159
|
-
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
160
|
-
decisionIds: stableUnique(finding.resolution.decisionIds),
|
|
161
|
-
}
|
|
162
|
-
: null,
|
|
163
|
-
})),
|
|
164
|
-
),
|
|
165
|
-
requirements: stableList(
|
|
166
|
-
prior.requirements.map((requirement) => ({
|
|
167
|
-
...requirement,
|
|
168
|
-
scope: normalizedScope(requirement.scope),
|
|
169
|
-
})),
|
|
170
|
-
),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const invalidFields = (error: z.ZodError) => ({
|
|
174
|
-
fields: error.issues.map((issue) => ({
|
|
175
|
-
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
176
|
-
reason: issue.message,
|
|
177
|
-
})),
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
function normalizeResolverInput(input: unknown): Result<NormalizedResolverInput> {
|
|
181
|
-
const parsed = resolverInputSchema.safeParse(input);
|
|
182
|
-
if (!parsed.success)
|
|
183
|
-
return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
184
|
-
|
|
185
|
-
const value = parsed.data;
|
|
186
|
-
const signals = value.assessment.signals;
|
|
187
|
-
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
188
|
-
return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
189
|
-
fields: [
|
|
190
|
-
{
|
|
191
|
-
path: "assessment.signals",
|
|
192
|
-
reason: "contradictory behaviorChange and mechanicalLowRisk",
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
});
|
|
196
|
-
const ids = value.constraints.map((constraint) => constraint.id);
|
|
26
|
+
/** The task policy for a judgment: derived requirements plus an input digest. */
|
|
27
|
+
export function resolvePolicy(input: ResolverInput): Result<Policy> {
|
|
28
|
+
const ids = input.constraints.map((constraint) => constraint.id);
|
|
197
29
|
if (new Set(ids).size !== ids.length)
|
|
198
30
|
return failure("invalid_input", "constraint IDs must be unique", {
|
|
199
31
|
fields: [{ path: "constraints", reason: "duplicate constraint id" }],
|
|
200
32
|
});
|
|
201
|
-
if (value.preferences?.fast && value.preferences.thorough)
|
|
202
|
-
return failure("invalid_input", "fast and thorough preferences contradict", {
|
|
203
|
-
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }],
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const normalized: NormalizedResolverInput = {
|
|
207
|
-
intent: normalizeIntent(value.intent),
|
|
208
|
-
assessment: normalizeAssessment(value.assessment),
|
|
209
|
-
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
210
|
-
prior: normalizePrior(value.prior),
|
|
211
|
-
...(value.preferences ? { preferences: { ...value.preferences } } : {}),
|
|
212
|
-
policyVersion: POLICY_VERSION,
|
|
213
|
-
};
|
|
214
|
-
return success(null, null, normalized);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
type RequirementSpec = {
|
|
218
|
-
ruleId: string;
|
|
219
|
-
dimension: Dimension;
|
|
220
|
-
scope: Scope;
|
|
221
|
-
reason: string;
|
|
222
|
-
satisfaction: string;
|
|
223
|
-
before: Requirement["before"];
|
|
224
|
-
dependentAction: string | null;
|
|
225
|
-
acceptanceAllowed: boolean;
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
const requirement = (spec: RequirementSpec): Requirement => ({
|
|
229
|
-
...spec,
|
|
230
|
-
id: requirementId({
|
|
231
|
-
ruleId: spec.ruleId,
|
|
232
|
-
scope: spec.scope,
|
|
233
|
-
satisfaction: spec.satisfaction,
|
|
234
|
-
before: spec.before,
|
|
235
|
-
dependentAction: spec.dependentAction,
|
|
236
|
-
}),
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const signalReason = (input: NormalizedResolverInput, name: keyof Assessment["signals"]): string =>
|
|
240
|
-
input.assessment.signals[name].reason;
|
|
241
|
-
|
|
242
|
-
function resolveRequirements(input: NormalizedResolverInput): Requirement[] {
|
|
243
|
-
const { signals } = input.assessment;
|
|
244
|
-
const scope = input.intent.scope;
|
|
245
|
-
const requirements: Requirement[] = [];
|
|
246
|
-
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
247
|
-
const hasUnknownFact = [
|
|
248
|
-
...input.assessment.facts,
|
|
249
|
-
...input.assessment.consequences.map(({ fact }) => fact),
|
|
250
|
-
].some((fact) => fact.basis === "unknown");
|
|
251
|
-
|
|
252
|
-
if (hasUnknownSignal || hasUnknownFact)
|
|
253
|
-
requirements.push(
|
|
254
|
-
requirement({
|
|
255
|
-
ruleId: "consequential-unknown",
|
|
256
|
-
dimension: "investigation",
|
|
257
|
-
scope,
|
|
258
|
-
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
259
|
-
...Object.entries(signals)
|
|
260
|
-
.filter(([, signal]) => signal.value === "unknown")
|
|
261
|
-
.map(([name]) => name),
|
|
262
|
-
...(hasUnknownFact ? ["unknown fact"] : []),
|
|
263
|
-
].join(", ")}`,
|
|
264
|
-
satisfaction:
|
|
265
|
-
"An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
266
|
-
before: "dependent_action",
|
|
267
|
-
dependentAction: null,
|
|
268
|
-
acceptanceAllowed: false,
|
|
269
|
-
}),
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
if (signals.productChoiceOpen.value === true)
|
|
273
|
-
requirements.push(
|
|
274
|
-
requirement({
|
|
275
|
-
ruleId: "product-decision",
|
|
276
|
-
dimension: "decisions",
|
|
277
|
-
scope,
|
|
278
|
-
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
279
|
-
satisfaction:
|
|
280
|
-
"Present the material alternatives and record the user's decision before the dependent action.",
|
|
281
|
-
before: "dependent_action",
|
|
282
|
-
dependentAction: null,
|
|
283
|
-
acceptanceAllowed: false,
|
|
284
|
-
}),
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
const independentBehaviorReview =
|
|
288
|
-
input.preferences?.thorough === true ||
|
|
289
|
-
input.assessment.consequences.some((consequence) =>
|
|
290
|
-
["security", "data", "public_contract", "operations"].includes(consequence.area),
|
|
291
|
-
);
|
|
292
|
-
if (signals.behaviorChange.value === true)
|
|
293
|
-
requirements.push(
|
|
294
|
-
requirement({
|
|
295
|
-
ruleId: "behavioral-verification",
|
|
296
|
-
dimension: "testing",
|
|
297
|
-
scope,
|
|
298
|
-
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
299
|
-
satisfaction:
|
|
300
|
-
"A useful behavioral check passes against the affected behavior and current candidate.",
|
|
301
|
-
before: "close",
|
|
302
|
-
dependentAction: null,
|
|
303
|
-
acceptanceAllowed: false,
|
|
304
|
-
}),
|
|
305
|
-
requirement({
|
|
306
|
-
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
307
|
-
dimension: "review",
|
|
308
|
-
scope,
|
|
309
|
-
reason: independentBehaviorReview
|
|
310
|
-
? "Consequential behavior or the thorough preference requires fresh-context review."
|
|
311
|
-
: "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
312
|
-
satisfaction: independentBehaviorReview
|
|
313
|
-
? "A separate review context examines the current candidate and records its result."
|
|
314
|
-
: "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
315
|
-
before: "close",
|
|
316
|
-
dependentAction: null,
|
|
317
|
-
acceptanceAllowed: !independentBehaviorReview,
|
|
318
|
-
}),
|
|
319
|
-
);
|
|
320
|
-
else if (signals.mechanicalLowRisk.value === true)
|
|
321
|
-
requirements.push(
|
|
322
|
-
requirement({
|
|
323
|
-
ruleId: "mechanical-existing-checks",
|
|
324
|
-
dimension: "verification",
|
|
325
|
-
scope,
|
|
326
|
-
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
327
|
-
satisfaction:
|
|
328
|
-
"Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
329
|
-
before: "close",
|
|
330
|
-
dependentAction: null,
|
|
331
|
-
acceptanceAllowed: true,
|
|
332
|
-
}),
|
|
333
|
-
requirement({
|
|
334
|
-
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
335
|
-
dimension: "review",
|
|
336
|
-
scope,
|
|
337
|
-
reason: input.preferences?.thorough
|
|
338
|
-
? "The thorough preference upgrades mechanical self-review to fresh-context review."
|
|
339
|
-
: "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
|
|
340
|
-
satisfaction: input.preferences?.thorough
|
|
341
|
-
? "A separate review context examines the current candidate and records its result."
|
|
342
|
-
: "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
343
|
-
before: "close",
|
|
344
|
-
dependentAction: null,
|
|
345
|
-
acceptanceAllowed: !input.preferences?.thorough,
|
|
346
|
-
}),
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
350
|
-
requirements.push(
|
|
351
|
-
requirement({
|
|
352
|
-
ruleId: "pre-pr-cleanup",
|
|
353
|
-
dimension: "verification",
|
|
354
|
-
scope,
|
|
355
|
-
reason: "Implementation output must be deslopped before it is delivered.",
|
|
356
|
-
satisfaction:
|
|
357
|
-
"Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
358
|
-
before: "dependent_action",
|
|
359
|
-
dependentAction: "hosting.pull_request",
|
|
360
|
-
acceptanceAllowed: true,
|
|
361
|
-
}),
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
if (signals.durableAgreementNeeded.value === true)
|
|
365
|
-
requirements.push(
|
|
366
|
-
requirement({
|
|
367
|
-
ruleId: "durable-spec",
|
|
368
|
-
dimension: "artifacts",
|
|
369
|
-
scope,
|
|
370
|
-
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
371
|
-
satisfaction:
|
|
372
|
-
"Record the agreed behavior in a durable specification before implementation writes.",
|
|
373
|
-
before: "write",
|
|
374
|
-
dependentAction: null,
|
|
375
|
-
acceptanceAllowed: true,
|
|
376
|
-
}),
|
|
377
|
-
);
|
|
378
|
-
if (signals.coordinationPlanNeeded.value === true)
|
|
379
|
-
requirements.push(
|
|
380
|
-
requirement({
|
|
381
|
-
ruleId: "coordination-plan",
|
|
382
|
-
dimension: "continuity",
|
|
383
|
-
scope,
|
|
384
|
-
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
385
|
-
satisfaction:
|
|
386
|
-
"Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
387
|
-
before: "dependent_action",
|
|
388
|
-
dependentAction: null,
|
|
389
|
-
acceptanceAllowed: true,
|
|
390
|
-
}),
|
|
391
|
-
);
|
|
392
|
-
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
393
|
-
requirements.push(
|
|
394
|
-
requirement({
|
|
395
|
-
ruleId: "helper-usefulness",
|
|
396
|
-
dimension: "delegation",
|
|
397
|
-
scope,
|
|
398
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
399
|
-
satisfaction:
|
|
400
|
-
"Use a bounded helper only for the independent objective and reconcile its result.",
|
|
401
|
-
before: "dependent_action",
|
|
402
|
-
dependentAction: null,
|
|
403
|
-
acceptanceAllowed: true,
|
|
404
|
-
}),
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
for (const constraint of input.constraints)
|
|
408
|
-
for (const obligation of constraint.requires) {
|
|
409
|
-
const dimension =
|
|
410
|
-
obligation.method ??
|
|
411
|
-
(obligation.kind === "check"
|
|
412
|
-
? "verification"
|
|
413
|
-
: obligation.kind === "decision"
|
|
414
|
-
? "decisions"
|
|
415
|
-
: "challenge");
|
|
416
|
-
requirements.push(
|
|
417
|
-
requirement({
|
|
418
|
-
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
419
|
-
kind: obligation.kind,
|
|
420
|
-
scope: normalizedScope(obligation.scope),
|
|
421
|
-
refs: normalizedRefList(obligation.refs),
|
|
422
|
-
method: obligation.method,
|
|
423
|
-
before: obligation.before,
|
|
424
|
-
dependentAction: obligation.dependentAction,
|
|
425
|
-
})}`,
|
|
426
|
-
dimension,
|
|
427
|
-
scope: obligation.scope,
|
|
428
|
-
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
429
|
-
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
430
|
-
before: obligation.before,
|
|
431
|
-
dependentAction: obligation.dependentAction,
|
|
432
|
-
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision",
|
|
433
|
-
}),
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const seen = new Set<string>();
|
|
438
|
-
return requirements.filter((item) => {
|
|
439
|
-
if (seen.has(item.id)) return false;
|
|
440
|
-
seen.add(item.id);
|
|
441
|
-
return true;
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export function resolvePolicy(input: ResolverInput): Result<Policy> {
|
|
446
|
-
const normalized = normalizeResolverInput(input);
|
|
447
|
-
if (!normalized.ok) return normalized;
|
|
448
|
-
const requirements = resolveRequirements(normalized.data);
|
|
449
33
|
const policy: Policy = {
|
|
450
34
|
policyVersion: POLICY_VERSION,
|
|
451
|
-
inputDigest: sha256(
|
|
452
|
-
|
|
35
|
+
inputDigest: sha256(
|
|
36
|
+
canonicalJson({
|
|
37
|
+
judgment: input.judgment,
|
|
38
|
+
scope: input.intent.scope,
|
|
39
|
+
constraints: input.constraints,
|
|
40
|
+
verification: input.verification ?? "self",
|
|
41
|
+
policyVersion: POLICY_VERSION,
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
requirements: deriveRequirements(
|
|
45
|
+
input.judgment,
|
|
46
|
+
input.intent.scope,
|
|
47
|
+
input.constraints,
|
|
48
|
+
input.verification,
|
|
49
|
+
),
|
|
453
50
|
};
|
|
454
51
|
const parsed = policySchema.safeParse(policy);
|
|
455
52
|
if (!parsed.success)
|
|
456
|
-
return failure("invalid_input", "resolved policy is invalid",
|
|
53
|
+
return failure("invalid_input", "resolved policy is invalid", {
|
|
54
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
55
|
+
path: issue.path.join("."),
|
|
56
|
+
reason: issue.message,
|
|
57
|
+
})),
|
|
58
|
+
});
|
|
457
59
|
return success(null, null, parsed.data);
|
|
458
60
|
}
|
|
459
61
|
|
|
62
|
+
const compareCodeUnits = (left: string, right: string): number =>
|
|
63
|
+
left < right ? -1 : left > right ? 1 : 0;
|
|
64
|
+
|
|
460
65
|
export function diffPolicy(
|
|
461
66
|
previous: Policy | null,
|
|
462
67
|
next: Policy,
|