@orchestrator-claude/definitions 3.43.1 → 3.44.1

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.
Files changed (53) hide show
  1. package/agents/adversarial-reviewer.md +35 -29
  2. package/agents/agentic-architect.md +1 -1
  3. package/agents/api-extractor.md +1 -1
  4. package/agents/architecture-mediator.md +1 -1
  5. package/agents/azure-devops-engineer.md +1 -1
  6. package/agents/backend-architect.md +1 -1
  7. package/agents/business-rule-miner.md +1 -1
  8. package/agents/cicd-platform-engineer.md +1 -1
  9. package/agents/ciclo-engineer.md +1 -1
  10. package/agents/code-archaeologist.md +1 -1
  11. package/agents/contract-architect.md +1 -1
  12. package/agents/datamodel-architect.md +1 -1
  13. package/agents/frontend-architect.md +1 -1
  14. package/agents/frontend-engineer.md +1 -1
  15. package/agents/harness-architect.md +1 -1
  16. package/agents/harness-engineer.md +1 -1
  17. package/agents/implementer.md +1 -1
  18. package/agents/infra-architect.md +1 -1
  19. package/agents/legacy-discoverer.md +1 -1
  20. package/agents/legacy-synthesizer.md +1 -1
  21. package/agents/methodology-researcher.md +2 -1
  22. package/agents/operational-artifact-builder.md +1 -1
  23. package/agents/planner.md +1 -1
  24. package/agents/presentation-engineer.md +1 -1
  25. package/agents/python-data-engineer.md +1 -1
  26. package/agents/researcher.md +1 -1
  27. package/agents/reviewer.md +1 -1
  28. package/agents/schema-extractor.md +1 -1
  29. package/agents/specifier.md +1 -1
  30. package/agents/task-generator.md +1 -1
  31. package/hooks/agent-git-guard.mjs +0 -2
  32. package/hooks/dangling-guard.mjs +18 -3
  33. package/hooks/gate-guardian.mjs +8617 -273
  34. package/hooks/gate-guardian.ts +296 -62
  35. package/hooks/lib/api-client.ts +154 -28
  36. package/hooks/lib/artifact-gate.ts +518 -0
  37. package/hooks/lib/artifact-producers.ts +106 -0
  38. package/hooks/lib/gate-logic.ts +6 -5
  39. package/hooks/lib/workflow-artifact-client.ts +351 -0
  40. package/hooks/mailbox-listener.mjs +0 -2
  41. package/hooks/playwright-output-guard.mjs +0 -2
  42. package/hooks/post-implement.mjs +18 -3
  43. package/hooks/pre-agent-dispatch.mjs +18 -3
  44. package/hooks/security-gate-honest.mjs +0 -2
  45. package/hooks/session-start.mjs +18 -3
  46. package/hooks/subagent-start.mjs +18 -3
  47. package/hooks/subagent-stop.mjs +18 -3
  48. package/hooks/user-prompt.mjs +18 -3
  49. package/hooks/verification-before-completion.mjs +0 -2
  50. package/hooks/workflow-guard.mjs +504 -29
  51. package/hooks/workflow-guard.ts +370 -27
  52. package/manifest.json +46 -46
  53. package/package.json +1 -1
@@ -13,10 +13,17 @@
13
13
  * 6. classifyRisk(targetPhase) → riskLevel
14
14
  * 7. buildGateContext() + deriveGateStrategy() → GateDecision
15
15
  * 8. switch strategy:
16
- * AUTO_APPROVE → allow + telemetry context
16
+ * ALLOW_LOW_RISK → allow + context (no approval observed by this hook)
17
17
  * ASK_HUMAN → deny + approval instructions
18
18
  * BLOCK_AND_REQUIRE → deny + requiredAction
19
19
  * 9. log(HOOK, JSON summary)
20
+ *
21
+ * RFC-044 F3, S5 (ADR-031): every ALLOW except quick goes through ONE exit, allowAfterLedger(), which first runs the
22
+ * ledger step over the files on disk (lib/artifact-gate.ts): verify the sealed phases exhaustively (D-469), validate
23
+ * and register the files of the phase being left (with advancedBySession, without agentType: TD-390, D-485 (1)).
24
+ * A failure there is a DENY. No DENY registers or verifies. In PME REQ the adversarial verdict is read from
25
+ * workflows/<wf>/<P>/adversarial_review.md first, and from the content store only when there is no file (RF-020).
26
+ * quick mode never registers (Q-PL-4); what stays open on purpose is in TD-391.
20
27
  */
21
28
 
22
29
  import {
@@ -30,9 +37,21 @@ import {
30
37
  getGateCellMode,
31
38
  getAdversarialVerdict,
32
39
  outputPreToolUse,
40
+ type AdversarialVerdictResult,
33
41
  type PendingAction,
34
42
  } from './lib/api-client.js';
35
43
  import { classifyRisk, deriveGateStrategy, type GateContext } from './lib/gate-logic.js';
44
+ import {
45
+ ADVERSARIAL_REVIEW_TYPE,
46
+ defaultArtifactGateDeps,
47
+ fileAdversarialVerdict,
48
+ observeWorkflow,
49
+ registerBeforeAllow,
50
+ sessionOf,
51
+ type ArtifactGateDeps,
52
+ type LedgerContext,
53
+ type Observation,
54
+ } from './lib/artifact-gate.js';
36
55
 
37
56
  const HOOK = 'GATE-GUARDIAN';
38
57
 
@@ -40,7 +59,9 @@ const HOOK = 'GATE-GUARDIAN';
40
59
  * Maximum adversarial review iterations before the hard cap triggers.
41
60
  * When iteration >= PME_HARD_CAP the gate ALLOWS (escalation path) and logs
42
61
  * a BLOCKED-escalation warning. This prevents infinite review loops.
43
- * RFC-031 §4.4 documents this mechanic.
62
+ * RFC-031 §4.4 documents this mechanic. The cap only applies to a well-formed
63
+ * `iteration` (kind 'ok'); an `iteration` the helper rejected has no count to
64
+ * compare, so a non-PASS verdict with it is denied instead (TD-375).
44
65
  */
45
66
  const PME_HARD_CAP = 3;
46
67
 
@@ -65,16 +86,15 @@ export interface HookDeps {
65
86
  gateKey: string,
66
87
  token: string
67
88
  ) => Promise<'REQ' | 'OPT' | 'SKIP' | null>;
68
- /** PME Step 5.5: retrieve adversarial review verdict + iteration (fail-open: null) */
69
- getAdversarialVerdict: (
70
- workflowId: string,
71
- token: string
72
- ) => Promise<{ verdict: string; iteration: number } | null>;
89
+ /** PME Step 5.5: retrieve the classified adversarial review verdict (TD-374; never rejects) */
90
+ getAdversarialVerdict: (workflowId: string, token: string) => Promise<AdversarialVerdictResult>;
73
91
  output: (opts: { decision: 'allow' | 'deny'; reason?: string; context?: string }) => void;
74
92
  log: (prefix: string, msg: string) => void;
93
+ /** RFC-044 F3, S5: the disk + ledger step before every ALLOW (lib/artifact-gate.ts). */
94
+ artifacts: ArtifactGateDeps;
75
95
  }
76
96
 
77
- const defaultDeps: HookDeps = {
97
+ export const defaultDeps: HookDeps = {
78
98
  readStdin,
79
99
  getWorkflowMode,
80
100
  getToken,
@@ -84,24 +104,180 @@ const defaultDeps: HookDeps = {
84
104
  getAdversarialVerdict,
85
105
  output: outputPreToolUse,
86
106
  log,
107
+ artifacts: defaultArtifactGateDeps(),
87
108
  };
88
109
 
110
+ // ─────────────────────────────────────────────────────────────
111
+ // PME Step 5.5: one decision per AdversarialVerdictResult kind
112
+ // ─────────────────────────────────────────────────────────────
113
+
114
+ type Output = HookDeps['output'];
115
+ type Log = HookDeps['log'];
116
+ type GateOutput = Parameters<Output>[0];
117
+
118
+ const RESULT_KINDS: ReadonlySet<string> = new Set([
119
+ 'ok',
120
+ 'absent',
121
+ 'malformed',
122
+ 'iteration_rejected',
123
+ 'error',
124
+ ]);
125
+
126
+ /** null / undefined / unknown kind (a stale dep or a bug) is an unexpected error, never a throw. */
127
+ function normalizeVerdictResult(raw: unknown): AdversarialVerdictResult {
128
+ const known =
129
+ typeof raw === 'object' && raw !== null && 'kind' in raw && RESULT_KINDS.has(String(raw.kind));
130
+ return known ? (raw as AdversarialVerdictResult) : { kind: 'error', reason: 'unexpected' };
131
+ }
132
+
133
+ /** Applies the gate for an `ok` verdict: hard cap -> PASS -> DENY (unchanged texts). */
134
+ function decideOkVerdict(
135
+ result: Extract<AdversarialVerdictResult, { kind: 'ok' }>,
136
+ workflowId: string,
137
+ targetPhase: string,
138
+ output: Output,
139
+ log: Log
140
+ ): void {
141
+ if (result.iteration >= PME_HARD_CAP) {
142
+ // Hard cap reached — ALLOW with escalation log (prevents infinite loops)
143
+ log(
144
+ HOOK,
145
+ `PME Step 5.5: BLOCKED escalation at hard cap (iteration=${String(result.iteration)}) — ALLOW wf=${workflowId}`
146
+ );
147
+ output({
148
+ decision: 'allow',
149
+ context: `[GATE] PME adversarial gate: hard cap reached (iteration=${String(result.iteration)}/${String(PME_HARD_CAP)}), escalating.`,
150
+ });
151
+ return;
152
+ }
153
+ if (result.verdict === 'PASS') {
154
+ log(HOOK, `PME Step 5.5: adversarial verdict=PASS — ALLOW wf=${workflowId}`);
155
+ output({
156
+ decision: 'allow',
157
+ context: `[GATE] PME adversarial gate: verdict=PASS, proceeding to '${targetPhase}'.`,
158
+ });
159
+ return;
160
+ }
161
+ // Verdict is NEEDS_REVISION (or unknown) and below hard cap — DENY
162
+ log(
163
+ HOOK,
164
+ `PME Step 5.5: DENY verdict=${result.verdict} iteration=${String(result.iteration)} wf=${workflowId}`
165
+ );
166
+ output({
167
+ decision: 'deny',
168
+ reason: `Gate Guardian: adversarial_review required — verdict=${result.verdict}, iteration=${String(result.iteration)}. Resolve adversarial review before advancing.`,
169
+ });
170
+ }
171
+
172
+ /**
173
+ * `iteration` was a string that is not a canonical decimal integer (TD-375).
174
+ * PASS needs no hard cap, so it proceeds; any other verdict is denied with the
175
+ * way out spelled in the reason (the writer must fix the artifact).
176
+ */
177
+ function decideRejectedIteration(
178
+ result: Extract<AdversarialVerdictResult, { kind: 'iteration_rejected' }>,
179
+ workflowId: string,
180
+ targetPhase: string,
181
+ output: Output,
182
+ log: Log
183
+ ): void {
184
+ const { verdict, detail } = result;
185
+ if (verdict === 'PASS') {
186
+ log(
187
+ HOOK,
188
+ `PME Step 5.5: adversarial verdict=PASS with iteration_invalid (${detail}) — ALLOW wf=${workflowId}`
189
+ );
190
+ output({
191
+ decision: 'allow',
192
+ context: `[GATE] PME adversarial gate: verdict=PASS, iteration_invalid (${detail}), hard cap not evaluated, proceeding to '${targetPhase}'.`,
193
+ });
194
+ return;
195
+ }
196
+ log(HOOK, `PME Step 5.5: DENY verdict=${verdict} iteration_invalid (${detail}) wf=${workflowId}`);
197
+ output({
198
+ decision: 'deny',
199
+ reason: `Gate Guardian: adversarial_review iteration invalid (${detail}) with verdict=${verdict} — the hard cap cannot apply. Rewrite the adversarial_review artifact with customMetadata.iteration as a non-negative decimal integer (e.g. '1') and retry.`,
200
+ });
201
+ }
202
+
203
+ /**
204
+ * Maps one classified adversarial result to ALLOW/DENY. Fail-open except a rejected iteration and, under
205
+ * absentPolicy 'deny', an absent verdict.
206
+ * `absentPolicy` is the seam of Q-PL-3 (T-PM-09): 'allow' (the default) or 'deny', which the PME REQ call site
207
+ * passes since S6c — the reviewer can no longer write to the content store, so its file is the only channel.
208
+ * `expectedFile` names that file in the DENY reason (the actionable part: who did not write what).
209
+ */
210
+ export function decideAdversarialResult(
211
+ raw: unknown,
212
+ workflowId: string,
213
+ targetPhase: string,
214
+ output: Output,
215
+ log: Log,
216
+ absentPolicy: 'allow' | 'deny' = 'allow',
217
+ expectedFile?: string
218
+ ): void {
219
+ const result = normalizeVerdictResult(raw);
220
+ switch (result.kind) {
221
+ case 'ok':
222
+ decideOkVerdict(result, workflowId, targetPhase, output, log);
223
+ return;
224
+ case 'iteration_rejected':
225
+ decideRejectedIteration(result, workflowId, targetPhase, output, log);
226
+ return;
227
+ case 'absent':
228
+ if (absentPolicy === 'deny') {
229
+ log(
230
+ HOOK,
231
+ `PME Step 5.5: no adversarial artifact found — DENY (absentPolicy=deny) wf=${workflowId}`
232
+ );
233
+ const file =
234
+ expectedFile ??
235
+ `project-guidelines/workflows/${workflowId}/<phase>/${ADVERSARIAL_REVIEW_TYPE}.md`;
236
+ output({
237
+ decision: 'deny',
238
+ reason: `Gate Guardian: adversarial_review required — the adversarial-reviewer did not write ${file} and the content store has no verdict, so '${targetPhase}' cannot be entered. Dispatch the adversarial-reviewer for this phase (it writes that file) and retry.`,
239
+ });
240
+ return;
241
+ }
242
+ log(HOOK, `PME Step 5.5: no adversarial artifact found — fail-open ALLOW wf=${workflowId}`);
243
+ output({
244
+ decision: 'allow',
245
+ context: `[GATE] PME adversarial gate: no artifact found, fail-open ALLOW for '${targetPhase}'.`,
246
+ });
247
+ return;
248
+ case 'malformed': {
249
+ const detail = result.detail === undefined ? '' : ` detail=${result.detail}`;
250
+ log(
251
+ HOOK,
252
+ `PME Step 5.5: adversarial artifact present but malformed (${result.reason}${detail}) — fail-open ALLOW wf=${workflowId}`
253
+ );
254
+ output({
255
+ decision: 'allow',
256
+ context: `[GATE] PME adversarial gate: artifact malformed (${result.reason}), fail-open ALLOW for '${targetPhase}'.`,
257
+ });
258
+ return;
259
+ }
260
+ case 'error': {
261
+ const status = result.status === undefined ? '' : ` status=${String(result.status)}`;
262
+ log(
263
+ HOOK,
264
+ `PME Step 5.5: adversarial artifact unreadable (${result.reason}${status}) — fail-open ALLOW wf=${workflowId}`
265
+ );
266
+ output({
267
+ decision: 'allow',
268
+ context: `[GATE] PME adversarial gate: artifact could not be read (${result.reason}), fail-open ALLOW for '${targetPhase}'.`,
269
+ });
270
+ return;
271
+ }
272
+ }
273
+ }
274
+
89
275
  // ─────────────────────────────────────────────────────────────
90
276
  // Core logic (exported for testing)
91
277
  // ─────────────────────────────────────────────────────────────
92
278
 
93
279
  export async function mainWithDeps(deps: HookDeps): Promise<void> {
94
- const {
95
- readStdin,
96
- getWorkflowMode,
97
- getToken,
98
- getNextAction,
99
- getWorkflowTypeAndVariant,
100
- getGateCellMode,
101
- getAdversarialVerdict,
102
- output,
103
- log,
104
- } = deps;
280
+ const { readStdin, getWorkflowMode, getToken, output, log } = deps;
105
281
 
106
282
  const stdin = await readStdin();
107
283
  log(HOOK, 'PreToolUse advancePhase triggered');
@@ -111,7 +287,39 @@ export async function mainWithDeps(deps: HookDeps): Promise<void> {
111
287
 
112
288
  log(HOOK, `workflow=${workflowId} targetPhase=${targetPhase}`);
113
289
 
114
- // ── Step 2: Mode check — quick/interactive bypass gate entirely ──────────
290
+ // ── RFC-044 F3, S5: the ledger step, shared by every ALLOW (one exit point) ─────────
291
+ const session = sessionOf(stdin.session_id);
292
+ const ledgerCtx: LedgerContext = {
293
+ workflowId,
294
+ ...(session.kind === 'ok' && { advancedBySession: session.advancedBySession }),
295
+ };
296
+ let observation: Promise<Observation> | undefined;
297
+ const observe = (): Promise<Observation> =>
298
+ (observation ??= observeWorkflow(workflowId, deps.artifacts));
299
+
300
+ /** The ONE exit of every ALLOW (but quick): verify + register first; a failure there is a DENY. */
301
+ const allowAfterLedger = async (allow: GateOutput): Promise<boolean> => {
302
+ if (session.kind === 'omitted') {
303
+ log(HOOK, `advancedBySession omitted (session_id ${session.why}) wf=${workflowId}`);
304
+ }
305
+ const observed = await observe();
306
+ const outcome =
307
+ observed.kind === 'deny'
308
+ ? ({ ok: false, reason: observed.reason } as const)
309
+ : await registerBeforeAllow(observed, ledgerCtx, deps.artifacts, (m) => {
310
+ log(HOOK, m);
311
+ });
312
+ if (!outcome.ok) {
313
+ log(HOOK, `DENY (ledger step) wf=${workflowId}: ${outcome.reason}`);
314
+ output({ decision: 'deny', reason: outcome.reason });
315
+ return false;
316
+ }
317
+ log(HOOK, `${outcome.summary} wf=${workflowId}`);
318
+ output(allow);
319
+ return true;
320
+ };
321
+
322
+ // ── Step 2: Mode check — quick bypasses the gate; interactive skips its evaluation ──
115
323
  if (workflowId) {
116
324
  const mode = await getWorkflowMode(workflowId);
117
325
  log(HOOK, `workflow=${workflowId} mode=${mode ?? 'legacy'}`);
@@ -119,18 +327,18 @@ export async function mainWithDeps(deps: HookDeps): Promise<void> {
119
327
  if (mode === 'quick') {
120
328
  output({
121
329
  decision: 'allow',
122
- context: `[GATE] AUTO_APPROVE: quick mode skips gate evaluation for '${targetPhase}'.`,
330
+ context: `[GATE] ALLOW_QUICK_MODE: quick mode skips gate evaluation for '${targetPhase}'; no approval observed by this hook.`,
123
331
  });
124
332
  log(HOOK, `ALLOW quick mode phase=${targetPhase}`);
125
333
  return;
126
334
  }
127
335
 
128
336
  if (mode === 'interactive') {
129
- output({
337
+ const allowed = await allowAfterLedger({
130
338
  decision: 'allow',
131
- context: `[GATE] AUTO_APPROVE: interactive mode skips artifact gate evaluation for '${targetPhase}'.`,
339
+ context: `[GATE] ALLOW_INTERACTIVE_MODE: interactive mode skips artifact gate evaluation for '${targetPhase}'; no approval observed by this hook.`,
132
340
  });
133
- log(HOOK, `ALLOW interactive mode phase=${targetPhase}`);
341
+ if (allowed) log(HOOK, `ALLOW interactive mode phase=${targetPhase}`);
134
342
  return;
135
343
  }
136
344
  }
@@ -159,12 +367,49 @@ export async function mainWithDeps(deps: HookDeps): Promise<void> {
159
367
  return;
160
368
  }
161
369
 
370
+ let decided: GateOutput | undefined;
371
+ const capture: Output = (o) => {
372
+ decided ??= o;
373
+ };
374
+ await evaluateGate(deps, { workflowId, targetPhase, token, observe, output: capture });
375
+ if (decided === undefined) {
376
+ output({ decision: 'deny', reason: 'Gate Guardian: no decision was produced (bug); denying.' });
377
+ return;
378
+ }
379
+ if (decided.decision === 'deny') {
380
+ output(decided);
381
+ return;
382
+ }
383
+ await allowAfterLedger(decided);
384
+ }
385
+
386
+ interface GateInput {
387
+ workflowId: string;
388
+ targetPhase: string;
389
+ token: string;
390
+ observe: () => Promise<Observation>;
391
+ /** Captures the decision; the caller routes an ALLOW through the ledger step. */
392
+ output: Output;
393
+ }
394
+
395
+ /** Steps 5.5, 5 and 6–8: the decision itself, written to `input.output` (a capture), never to stdout. */
396
+ async function evaluateGate(deps: HookDeps, input: GateInput): Promise<void> {
397
+ const { getNextAction, getWorkflowTypeAndVariant, getGateCellMode, getAdversarialVerdict, log } =
398
+ deps;
399
+ const { workflowId, targetPhase, token, observe, output } = input;
400
+
162
401
  // ── Step 5.5: PME adversarial gate enforcement (RFC-031 §4.4) ───────────
163
402
  // Only enforced when:
164
403
  // a) workflow type is 'project_management_enterprise'
165
404
  // b) the variant's 'adversarial' gate cell is 'REQ'
166
405
  // c) adversarial review artifact exists and verdict is not PASS
167
- // All infra failures are fail-open (ALLOW) so hooks never block on downtime.
406
+ // Malformed / unreadable artifacts are fail-open (ALLOW) so hooks never block
407
+ // on downtime; each is logged under its own label (TD-374). Exceptions: a
408
+ // non-PASS verdict whose `iteration` is not a canonical decimal integer is
409
+ // denied (TD-375), and since S6c (T-S6c-03, T-PM-09) an ABSENT verdict is
410
+ // denied too — the reviewer's vault file is its only channel, so no file and no
411
+ // content-store verdict means the reviewer did not write (R-12). PME non-REQ,
412
+ // quick and interactive never reach this branch.
168
413
  {
169
414
  const typeVariant = await getWorkflowTypeAndVariant(workflowId, token);
170
415
  if (typeVariant?.type === 'project_management_enterprise') {
@@ -175,49 +420,38 @@ export async function mainWithDeps(deps: HookDeps): Promise<void> {
175
420
  token
176
421
  );
177
422
  if (gateMode === 'REQ') {
178
- const verdict = await getAdversarialVerdict(workflowId, token);
179
- if (!verdict) {
180
- // No artifact yet — fail-open: ALLOW
181
- log(
182
- HOOK,
183
- `PME Step 5.5: no adversarial artifact found — fail-open ALLOW wf=${workflowId}`
184
- );
185
- output({
186
- decision: 'allow',
187
- context: `[GATE] PME adversarial gate: no artifact found, fail-open ALLOW for '${targetPhase}'.`,
188
- });
189
- return;
190
- }
191
- if (verdict.iteration >= PME_HARD_CAP) {
192
- // Hard cap reached — ALLOW with escalation log (prevents infinite loops)
193
- log(
194
- HOOK,
195
- `PME Step 5.5: BLOCKED escalation at hard cap (iteration=${String(verdict.iteration)}) — ALLOW wf=${workflowId}`
196
- );
197
- output({
198
- decision: 'allow',
199
- context: `[GATE] PME adversarial gate: hard cap reached (iteration=${String(verdict.iteration)}/${String(PME_HARD_CAP)}), escalating.`,
200
- });
423
+ // RF-020: the file of the phase being left comes first; the content store only when there is none.
424
+ const observed = await observe();
425
+ if (observed.kind === 'deny') {
426
+ output({ decision: 'deny', reason: observed.reason });
201
427
  return;
202
428
  }
203
- if (verdict.verdict === 'PASS') {
204
- // Adversarial review passed — ALLOW
205
- log(HOOK, `PME Step 5.5: adversarial verdict=PASS — ALLOW wf=${workflowId}`);
429
+ const fromFile = fileAdversarialVerdict(observed);
430
+ if (fromFile.kind === 'invalid') {
431
+ log(HOOK, `PME Step 5.5: DENY adversarial_review file invalid wf=${workflowId}`);
206
432
  output({
207
- decision: 'allow',
208
- context: `[GATE] PME adversarial gate: verdict=PASS, proceeding to '${targetPhase}'.`,
433
+ decision: 'deny',
434
+ reason: `Gate Guardian: adversarial_review file invalid — ${fromFile.reason} Rewrite it with a string verdict and an integer iteration (ADR-031 D3).`,
209
435
  });
210
436
  return;
211
437
  }
212
- // Verdict is NEEDS_REVISION (or unknown) and below hard cap — DENY
438
+ const result =
439
+ fromFile.kind === 'verdict'
440
+ ? fromFile.result
441
+ : await getAdversarialVerdict(workflowId, token);
213
442
  log(
214
443
  HOOK,
215
- `PME Step 5.5: DENY verdict=${verdict.verdict} iteration=${String(verdict.iteration)} wf=${workflowId}`
444
+ `PME Step 5.5: verdict source=${fromFile.kind === 'verdict' ? 'file' : 'content-store'} wf=${workflowId}`
445
+ );
446
+ decideAdversarialResult(
447
+ result,
448
+ workflowId,
449
+ targetPhase,
450
+ output,
451
+ log,
452
+ 'deny',
453
+ `project-guidelines/workflows/${workflowId}/${observed.phase}/${ADVERSARIAL_REVIEW_TYPE}.md`
216
454
  );
217
- output({
218
- decision: 'deny',
219
- reason: `Gate Guardian: adversarial_review required — verdict=${verdict.verdict}, iteration=${String(verdict.iteration)}. Resolve adversarial review before advancing.`,
220
- });
221
455
  return;
222
456
  }
223
457
  // gateMode is OPT, SKIP, or null — fall through to Step 5
@@ -236,7 +470,7 @@ export async function mainWithDeps(deps: HookDeps): Promise<void> {
236
470
 
237
471
  // ── Step 5: IMPLEMENT short-circuit (RF-008) ─────────────────────────────
238
472
  // If targetPhase contains "implement", check pending-action BEFORE classifyRisk.
239
- // This prevents AUTO_APPROVE from being applied to implement phases.
473
+ // This prevents the low-risk allow path from being applied to implement phases.
240
474
  //
241
475
  // Phase 4: Only 'implement' phases have the approval path (via pending-action).
242
476
  // Other HIGH-risk phases (deploy, migrate, delete) will always ASK_HUMAN → DENY.
@@ -303,10 +537,10 @@ export async function mainWithDeps(deps: HookDeps): Promise<void> {
303
537
  );
304
538
 
305
539
  switch (decision.strategy) {
306
- case 'AUTO_APPROVE':
540
+ case 'ALLOW_LOW_RISK':
307
541
  output({
308
542
  decision: 'allow',
309
- context: `[GATE] AUTO_APPROVE: ${decision.reason}`,
543
+ context: `[GATE] ALLOW_LOW_RISK: ${decision.reason}`,
310
544
  });
311
545
  break;
312
546