@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.
@@ -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
- export type ResolverPrior = {
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
- assessment: Assessment;
21
+ judgment: Judgment;
44
22
  constraints: Constraint[];
45
- prior: ResolverPrior;
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
- const stableUnique = (values: string[]): string[] =>
80
- [...new Set(values)].toSorted(compareCodeUnits);
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(canonicalJson(normalized.data)),
452
- requirements,
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", invalidFields(parsed.error));
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,