@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.
- package/agents/adversarial-reviewer.md +35 -29
- package/agents/agentic-architect.md +1 -1
- package/agents/api-extractor.md +1 -1
- package/agents/architecture-mediator.md +1 -1
- package/agents/azure-devops-engineer.md +1 -1
- package/agents/backend-architect.md +1 -1
- package/agents/business-rule-miner.md +1 -1
- package/agents/cicd-platform-engineer.md +1 -1
- package/agents/ciclo-engineer.md +1 -1
- package/agents/code-archaeologist.md +1 -1
- package/agents/contract-architect.md +1 -1
- package/agents/datamodel-architect.md +1 -1
- package/agents/frontend-architect.md +1 -1
- package/agents/frontend-engineer.md +1 -1
- package/agents/harness-architect.md +1 -1
- package/agents/harness-engineer.md +1 -1
- package/agents/implementer.md +1 -1
- package/agents/infra-architect.md +1 -1
- package/agents/legacy-discoverer.md +1 -1
- package/agents/legacy-synthesizer.md +1 -1
- package/agents/methodology-researcher.md +2 -1
- package/agents/operational-artifact-builder.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/presentation-engineer.md +1 -1
- package/agents/python-data-engineer.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/schema-extractor.md +1 -1
- package/agents/specifier.md +1 -1
- package/agents/task-generator.md +1 -1
- package/hooks/agent-git-guard.mjs +0 -2
- package/hooks/dangling-guard.mjs +18 -3
- package/hooks/gate-guardian.mjs +8617 -273
- package/hooks/gate-guardian.ts +296 -62
- package/hooks/lib/api-client.ts +154 -28
- package/hooks/lib/artifact-gate.ts +518 -0
- package/hooks/lib/artifact-producers.ts +106 -0
- package/hooks/lib/gate-logic.ts +6 -5
- package/hooks/lib/workflow-artifact-client.ts +351 -0
- package/hooks/mailbox-listener.mjs +0 -2
- package/hooks/playwright-output-guard.mjs +0 -2
- package/hooks/post-implement.mjs +18 -3
- package/hooks/pre-agent-dispatch.mjs +18 -3
- package/hooks/security-gate-honest.mjs +0 -2
- package/hooks/session-start.mjs +18 -3
- package/hooks/subagent-start.mjs +18 -3
- package/hooks/subagent-stop.mjs +18 -3
- package/hooks/user-prompt.mjs +18 -3
- package/hooks/verification-before-completion.mjs +0 -2
- package/hooks/workflow-guard.mjs +504 -29
- package/hooks/workflow-guard.ts +370 -27
- package/manifest.json +46 -46
- package/package.json +1 -1
package/hooks/lib/api-client.ts
CHANGED
|
@@ -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
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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:
|
|
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
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
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<
|
|
1228
|
+
): Promise<AdversarialVerdictResult> {
|
|
1106
1229
|
try {
|
|
1107
|
-
const
|
|
1108
|
-
content
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
1113
|
-
if (
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
|
1245
|
+
return { kind: 'error', reason: 'unexpected' };
|
|
1120
1246
|
}
|
|
1121
1247
|
}
|
|
1122
1248
|
|