@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
@@ -123,12 +123,27 @@ const API_URL = process.env.ORCHESTRATOR_API_URL || 'http://localhost:3001';
123
123
  // `system` tier account (level 4, ABOVE admin) rather than admin — the
124
124
  // env-override chain is preserved verbatim so an explicit
125
125
  // ORCHESTRATOR_ADMIN_EMAIL/ORCHESTRATOR_AUTH_EMAIL still wins.
126
- const AUTH_EMAIL =
127
- process.env.ORCHESTRATOR_ADMIN_EMAIL ||
128
- process.env.ORCHESTRATOR_AUTH_EMAIL ||
129
- 'system@orchestrator.local';
130
- const AUTH_PASSWORD =
131
- process.env.ORCHESTRATOR_ADMIN_PASSWORD || process.env.ORCHESTRATOR_AUTH_PASSWORD || 'system123';
126
+ const DEFAULT_AUTH_EMAIL = 'system@orchestrator.local';
127
+ // TD-267 A1 (S572): there is NO default password. The credential is read
128
+ // lazily, at login time, and a password that is unset, empty or
129
+ // whitespace-only counts as absent — in which case login() makes no network
130
+ // call at all and getToken() returns null (each hook keeps the posture it
131
+ // already has for "no token"). The client never invents a credential.
132
+ const MISSING_CREDENTIAL_MESSAGE =
133
+ 'no credential configured (ORCHESTRATOR_ADMIN_PASSWORD / ORCHESTRATOR_AUTH_PASSWORD unset or empty) - login not attempted';
134
+ let missingCredentialLogged = false;
135
+
136
+ function resolveAuthCredentials(): { email: string; password: string } | null {
137
+ const password =
138
+ (process.env.ORCHESTRATOR_ADMIN_PASSWORD ?? '').trim() ||
139
+ (process.env.ORCHESTRATOR_AUTH_PASSWORD ?? '').trim();
140
+ if (!password) return null;
141
+ const email =
142
+ process.env.ORCHESTRATOR_ADMIN_EMAIL ||
143
+ process.env.ORCHESTRATOR_AUTH_EMAIL ||
144
+ DEFAULT_AUTH_EMAIL;
145
+ return { email, password };
146
+ }
132
147
  // Mutable (not const): RFC-040 Operator Multi-Project, Slice 5 (C5) lazily
133
148
  // resolves this once per process when the env var is absent — see
134
149
  // ensureProjectIdResolved() below.
@@ -338,6 +353,14 @@ async function login(): Promise<string | null> {
338
353
  // the server ignores X-Project-ID on this route entirely, so login can
339
354
  // always proceed and PROJECT_ID is resolved afterwards, once a Bearer
340
355
  // token is available (see ensureProjectIdResolved, called from getToken()).
356
+ const credentials = resolveAuthCredentials();
357
+ if (credentials === null) {
358
+ if (!missingCredentialLogged) {
359
+ missingCredentialLogged = true;
360
+ log('API-CLIENT', MISSING_CREDENTIAL_MESSAGE);
361
+ }
362
+ return null;
363
+ }
341
364
  try {
342
365
  const resp = await fetch(`${API_URL}/api/v1/auth/login`, {
343
366
  method: 'POST',
@@ -345,7 +368,7 @@ async function login(): Promise<string | null> {
345
368
  'Content-Type': 'application/json',
346
369
  'X-Project-ID': PROJECT_ID,
347
370
  },
348
- body: JSON.stringify({ email: AUTH_EMAIL, password: AUTH_PASSWORD }),
371
+ body: JSON.stringify({ email: credentials.email, password: credentials.password }),
349
372
  signal: AbortSignal.timeout(5000),
350
373
  });
351
374
  if (!resp.ok) return null;
@@ -1088,35 +1111,138 @@ export async function getGateCellMode(
1088
1111
  }
1089
1112
  }
1090
1113
 
1114
+ /**
1115
+ * Outcome of `GET /api/v1/artifact-content/:wf/adversarial_review`, classified
1116
+ * so the caller can tell WHY there is no usable verdict (TD-374) and decide.
1117
+ *
1118
+ * - `ok`: verdict string + `iteration` parsed from a canonical decimal string.
1119
+ * - `absent`: HTTP 404 (no artifact yet).
1120
+ * - `malformed`: HTTP 200 whose body carries no decidable verdict. `iteration_invalid`
1121
+ * here means `iteration` is absent or not a string.
1122
+ * - `iteration_rejected`: verdict is a valid string but `iteration` is a string that
1123
+ * fails the grammar (TD-375). Distinct kind on purpose: the caller must decide, a
1124
+ * `malformed` branch handled as fail-open would reopen the hole in silence.
1125
+ * - `error`: no usable response (HTTP != 200/404, timeout, network, unexpected).
1126
+ */
1127
+ export type AdversarialVerdictResult =
1128
+ | { kind: 'ok'; verdict: string; iteration: number }
1129
+ | { kind: 'absent' }
1130
+ | {
1131
+ kind: 'malformed';
1132
+ reason:
1133
+ | 'body_not_json'
1134
+ | 'body_not_object'
1135
+ | 'custom_metadata_missing'
1136
+ | 'verdict_invalid'
1137
+ | 'iteration_invalid';
1138
+ detail?: string;
1139
+ }
1140
+ | { kind: 'iteration_rejected'; verdict: string; detail: string }
1141
+ | { kind: 'error'; reason: 'http' | 'timeout' | 'network' | 'unexpected'; status?: number };
1142
+
1143
+ type StatusFetch =
1144
+ | { readonly kind: 'response'; readonly resp: Response }
1145
+ | { readonly kind: 'error'; readonly reason: 'timeout' | 'network' };
1146
+
1147
+ /**
1148
+ * Same request as `apiGet` (URL, headers, 5s timeout) but keeps the HTTP
1149
+ * status and the failure cause that `apiGet` collapses into `null`.
1150
+ * `apiGet` itself is intentionally left untouched.
1151
+ */
1152
+ async function apiGetWithStatus(path: string, token: string): Promise<StatusFetch> {
1153
+ try {
1154
+ const resp = await fetch(`${API_URL}${path}`, {
1155
+ headers: {
1156
+ Authorization: `Bearer ${token}`,
1157
+ 'X-Project-ID': PROJECT_ID,
1158
+ },
1159
+ signal: AbortSignal.timeout(5000),
1160
+ });
1161
+ return { kind: 'response', resp };
1162
+ } catch (err) {
1163
+ const name: unknown = typeof err === 'object' && err !== null && 'name' in err ? err.name : '';
1164
+ const timedOut = name === 'TimeoutError' || name === 'AbortError';
1165
+ return { kind: 'error', reason: timedOut ? 'timeout' : 'network' };
1166
+ }
1167
+ }
1168
+
1169
+ /** `JSON.stringify(raw)` capped at 40 chars; undefined when raw has no JSON form. */
1170
+ function iterationDetail(raw: unknown): string | undefined {
1171
+ // JSON.stringify yields undefined for undefined/functions/symbols despite its typing.
1172
+ const json = JSON.stringify(raw) as string | undefined;
1173
+ return json === undefined ? undefined : json.slice(0, 40);
1174
+ }
1175
+
1176
+ /** Canonical decimal grammar for `iteration` (TD-375): ASCII digits, safe integer. */
1177
+ function parseIteration(raw: string): number | null {
1178
+ if (!/^[0-9]+$/.test(raw)) return null;
1179
+ const value = Number(raw);
1180
+ return Number.isSafeInteger(value) ? value : null;
1181
+ }
1182
+
1183
+ function classifyIteration(verdict: string, rawIteration: unknown): AdversarialVerdictResult {
1184
+ if (typeof rawIteration !== 'string') {
1185
+ const detail = iterationDetail(rawIteration);
1186
+ return detail === undefined
1187
+ ? { kind: 'malformed', reason: 'iteration_invalid' }
1188
+ : { kind: 'malformed', reason: 'iteration_invalid', detail };
1189
+ }
1190
+ const iteration = parseIteration(rawIteration);
1191
+ if (iteration === null) {
1192
+ return { kind: 'iteration_rejected', verdict, detail: iterationDetail(rawIteration) ?? '' };
1193
+ }
1194
+ return { kind: 'ok', verdict, iteration };
1195
+ }
1196
+
1197
+ function classifyAdversarialBody(body: unknown): AdversarialVerdictResult {
1198
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
1199
+ return { kind: 'malformed', reason: 'body_not_object' };
1200
+ }
1201
+ const metadata: unknown = 'metadata' in body ? body.metadata : undefined;
1202
+ const customMetadata: unknown =
1203
+ typeof metadata === 'object' && metadata !== null && 'customMetadata' in metadata
1204
+ ? metadata.customMetadata
1205
+ : undefined;
1206
+ if (typeof customMetadata !== 'object' || customMetadata === null) {
1207
+ return { kind: 'malformed', reason: 'custom_metadata_missing' };
1208
+ }
1209
+ const verdict: unknown = 'verdict' in customMetadata ? customMetadata.verdict : undefined;
1210
+ if (typeof verdict !== 'string') return { kind: 'malformed', reason: 'verdict_invalid' };
1211
+ const rawIteration: unknown =
1212
+ 'iteration' in customMetadata ? customMetadata.iteration : undefined;
1213
+ return classifyIteration(verdict, rawIteration);
1214
+ }
1215
+
1091
1216
  /**
1092
1217
  * Retrieves the adversarial review verdict and iteration for a workflow.
1093
- * Calls GET /api/v1/artifact-content/:workflowId/adversarial_review.
1094
- * The API serves the fields in metadata.customMetadata (strings — S3
1095
- * x-amz-meta-* headers only carry strings), so `iteration` is coerced:
1096
- * accepted only as a non-blank string whose Number() is finite (TD-331).
1097
- * Returns null when artifact is absent (404), on 5xx, timeout, or missing /
1098
- * malformed customMetadata fields (fail-open — gate is not enforced when
1099
- * artifact is absent). The pre-TD-331 flat metadata.{verdict,iteration}
1100
- * shape is NOT read (RF-003).
1218
+ * Calls GET /api/v1/artifact-content/:workflowId/adversarial_review and
1219
+ * classifies the outcome (see AdversarialVerdictResult); it never rejects and
1220
+ * never returns null. The API serves the fields in metadata.customMetadata
1221
+ * (strings: S3 x-amz-meta-* headers only carry strings), so `iteration` is
1222
+ * accepted only as a canonical decimal string (/^[0-9]+$/, safe integer). The
1223
+ * pre-TD-331 flat metadata.{verdict,iteration} shape is NOT read (RF-003).
1101
1224
  */
1102
1225
  export async function getAdversarialVerdict(
1103
1226
  workflowId: string,
1104
1227
  token: string
1105
- ): Promise<{ verdict: string; iteration: number } | null> {
1228
+ ): Promise<AdversarialVerdictResult> {
1106
1229
  try {
1107
- const resp = await apiGet<{
1108
- content?: string;
1109
- metadata?: { customMetadata?: Record<string, unknown> };
1110
- }>(`/api/v1/artifact-content/${workflowId}/adversarial_review`, token);
1111
- if (!resp) return null;
1112
- const { verdict, iteration: rawIteration } = resp.metadata?.customMetadata ?? {};
1113
- if (typeof verdict !== 'string' || typeof rawIteration !== 'string') return null;
1114
- if (rawIteration.trim() === '') return null;
1115
- const iteration = Number(rawIteration);
1116
- if (!Number.isFinite(iteration)) return null;
1117
- return { verdict, iteration };
1230
+ const got = await apiGetWithStatus(
1231
+ `/api/v1/artifact-content/${workflowId}/adversarial_review`,
1232
+ token
1233
+ );
1234
+ if (got.kind === 'error') return got;
1235
+ if (got.resp.status === 404) return { kind: 'absent' };
1236
+ if (!got.resp.ok) return { kind: 'error', reason: 'http', status: got.resp.status };
1237
+ let body: unknown;
1238
+ try {
1239
+ body = await got.resp.json();
1240
+ } catch {
1241
+ return { kind: 'malformed', reason: 'body_not_json' };
1242
+ }
1243
+ return classifyAdversarialBody(body);
1118
1244
  } catch {
1119
- return null;
1245
+ return { kind: 'error', reason: 'unexpected' };
1120
1246
  }
1121
1247
  }
1122
1248