devcontour-harness 0.1.0-alpha.5 → 0.1.0-alpha.7
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 +42 -2
- package/docs/compatibility-versions.md +56 -0
- package/docs/compatibility.md +2 -2
- package/docs/connectors.md +323 -0
- package/docs/contracts.md +33 -5
- package/docs/integrations/hindsight.md +148 -0
- package/docs/quickstart.md +1 -1
- package/docs/release-notes/0.1.0-alpha.6.md +238 -0
- package/docs/release-notes/0.1.0-alpha.7.md +71 -0
- package/docs/review-probes.md +199 -0
- package/docs/review-protocol.md +251 -0
- package/docs/toolchains.md +167 -0
- package/docs/turbo-starter.md +269 -0
- package/docs/upgrade.md +77 -24
- package/examples/README.md +1 -0
- package/examples/toolchains/README.md +32 -0
- package/examples/toolchains/go/bad/label/label.go +9 -0
- package/examples/toolchains/go/good/label/label.go +9 -0
- package/examples/toolchains/go/policy.json +79 -0
- package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/go/repo/_gitignore +1 -0
- package/examples/toolchains/go/repo/go.mod +5 -0
- package/examples/toolchains/go/repo/go.sum +2 -0
- package/examples/toolchains/go/repo/label/label.go +9 -0
- package/examples/toolchains/go/repo/label/label_test.go +27 -0
- package/examples/toolchains/go/repo/label/version.go +9 -0
- package/examples/toolchains/go/task.json +25 -0
- package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/policy.json +126 -0
- package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
- package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-go/task.json +25 -0
- package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/policy.json +121 -0
- package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
- package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
- package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
- package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
- package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-python/task.json +25 -0
- package/examples/toolchains/npm/bad/src/label.js +4 -0
- package/examples/toolchains/npm/good/src/label.js +4 -0
- package/examples/toolchains/npm/policy.json +82 -0
- package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/npm/repo/_gitignore +4 -0
- package/examples/toolchains/npm/repo/package-lock.json +22 -0
- package/examples/toolchains/npm/repo/package.json +13 -0
- package/examples/toolchains/npm/repo/src/label.js +4 -0
- package/examples/toolchains/npm/repo/test/label.test.js +13 -0
- package/examples/toolchains/npm/task.json +25 -0
- package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/policy.json +85 -0
- package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/pnpm/repo/_gitignore +4 -0
- package/examples/toolchains/pnpm/repo/package.json +10 -0
- package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
- package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
- package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/pnpm/task.json +25 -0
- package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/policy.json +80 -0
- package/examples/toolchains/python/repo/.python-version +1 -0
- package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/python/repo/_gitignore +5 -0
- package/examples/toolchains/python/repo/pyproject.toml +19 -0
- package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/repo/tests/test_label.py +13 -0
- package/examples/toolchains/python/repo/uv.lock +108 -0
- package/examples/toolchains/python/task.json +25 -0
- package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/policy.json +110 -0
- package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/turbo/repo/_gitignore +4 -0
- package/examples/toolchains/turbo/repo/package.json +13 -0
- package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
- package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
- package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/turbo/repo/turbo.json +7 -0
- package/examples/toolchains/turbo/task.json +25 -0
- package/examples/turbo-starter/README.md +16 -0
- package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
- package/examples/turbo-starter/changes/issued-at.patch +130 -0
- package/examples/turbo-starter/cleanup.mjs +58 -0
- package/examples/turbo-starter/graph.json +32 -0
- package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
- package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
- package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
- package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
- package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
- package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
- package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
- package/examples/turbo-starter/harness/env/backend.env.template +7 -0
- package/examples/turbo-starter/harness/env/compose.env.template +6 -0
- package/examples/turbo-starter/policy.json +109 -0
- package/examples/turbo-starter/policy.lifecycle.json +114 -0
- package/examples/turbo-starter/setup.mjs +352 -0
- package/lib/agents/cli-adapter.js +10 -1
- package/lib/agents/inspect.d.ts +56 -2
- package/lib/agents/inspect.js +264 -14
- package/lib/agents/review-probe.d.ts +229 -7
- package/lib/agents/review-probe.js +830 -72
- package/lib/agents/schemas.d.ts +3 -1
- package/lib/agents/schemas.js +7 -3
- package/lib/cli.js +167 -7
- package/lib/contracts/cli.d.ts +820 -11
- package/lib/contracts/cli.js +243 -4
- package/lib/contracts/errors.d.ts +3 -1
- package/lib/contracts/errors.js +2 -0
- package/lib/contracts/events.d.ts +37 -5
- package/lib/contracts/events.js +9 -0
- package/lib/contracts/execution.d.ts +389 -15
- package/lib/contracts/execution.js +76 -4
- package/lib/contracts/failure.js +5 -0
- package/lib/contracts/index.d.ts +1 -0
- package/lib/contracts/index.js +1 -0
- package/lib/contracts/policy.d.ts +15 -0
- package/lib/contracts/policy.js +10 -0
- package/lib/contracts/ports.d.ts +50 -0
- package/lib/contracts/profiles.d.ts +14 -0
- package/lib/contracts/profiles.js +4 -0
- package/lib/contracts/published.d.ts +279 -8
- package/lib/contracts/result.d.ts +171 -0
- package/lib/contracts/result.js +39 -1
- package/lib/contracts/review.d.ts +231 -0
- package/lib/contracts/review.js +326 -2
- package/lib/contracts/toolchain.d.ts +96 -0
- package/lib/contracts/toolchain.js +89 -0
- package/lib/control/doctor.d.ts +16 -1
- package/lib/control/doctor.js +45 -3
- package/lib/control/harness.d.ts +52 -6
- package/lib/control/harness.js +28 -1
- package/lib/control/recovery.d.ts +3 -1
- package/lib/control/review.js +23 -11
- package/lib/engine/budget.d.ts +3 -3
- package/lib/engine/classify.js +1 -0
- package/lib/engine/engine.d.ts +67 -17
- package/lib/engine/prompt.d.ts +29 -1
- package/lib/engine/prompt.js +57 -0
- package/lib/engine/property-recovery.js +2 -0
- package/lib/engine/result.js +52 -0
- package/lib/engine/reuse.js +6 -1
- package/lib/engine/runner.d.ts +25 -0
- package/lib/engine/runner.js +500 -123
- package/lib/environment/lifecycle.js +6 -0
- package/lib/environment/live-calls.js +3 -2
- package/lib/environment/os-tools.d.ts +16 -0
- package/lib/environment/os-tools.js +35 -0
- package/lib/environment/process.d.ts +8 -0
- package/lib/environment/process.js +13 -6
- package/lib/integrations/connector/commands.d.ts +358 -0
- package/lib/integrations/connector/commands.js +428 -0
- package/lib/integrations/connector/config.d.ts +215 -0
- package/lib/integrations/connector/config.js +214 -0
- package/lib/integrations/connector/delivery.d.ts +107 -0
- package/lib/integrations/connector/delivery.js +591 -0
- package/lib/integrations/connector/fake.d.ts +291 -0
- package/lib/integrations/connector/fake.js +786 -0
- package/lib/integrations/connector/history.d.ts +124 -0
- package/lib/integrations/connector/history.js +497 -0
- package/lib/integrations/connector/index.d.ts +16 -0
- package/lib/integrations/connector/index.js +16 -0
- package/lib/integrations/connector/redact.d.ts +19 -0
- package/lib/integrations/connector/redact.js +91 -0
- package/lib/integrations/connector/registry.d.ts +15 -0
- package/lib/integrations/connector/registry.js +43 -0
- package/lib/integrations/connector/snapshot.d.ts +131 -0
- package/lib/integrations/connector/snapshot.js +422 -0
- package/lib/integrations/connector/source.d.ts +37 -0
- package/lib/integrations/connector/source.js +73 -0
- package/lib/integrations/connector/steps.d.ts +71 -0
- package/lib/integrations/connector/steps.js +188 -0
- package/lib/integrations/connector/store.d.ts +140 -0
- package/lib/integrations/connector/store.js +277 -0
- package/lib/integrations/connector/summary.d.ts +38 -0
- package/lib/integrations/connector/summary.js +59 -0
- package/lib/integrations/connector/testing-worker.d.ts +1 -0
- package/lib/integrations/connector/testing-worker.js +81 -0
- package/lib/integrations/connector/testing.d.ts +205 -0
- package/lib/integrations/connector/testing.js +1615 -0
- package/lib/integrations/connector/transport.d.ts +240 -0
- package/lib/integrations/connector/transport.js +28 -0
- package/lib/integrations/memory/context.d.ts +120 -0
- package/lib/integrations/memory/context.js +239 -0
- package/lib/integrations/memory/index.d.ts +8 -0
- package/lib/integrations/memory/index.js +8 -0
- package/lib/integrations/memory/provider.d.ts +79 -0
- package/lib/integrations/memory/provider.js +22 -0
- package/lib/integrations/memory/sink.d.ts +56 -0
- package/lib/integrations/memory/sink.js +116 -0
- package/lib/integrations/memory/testing.d.ts +44 -0
- package/lib/integrations/memory/testing.js +61 -0
- package/lib/integrations/relay-v1/index.d.ts +122 -0
- package/lib/integrations/relay-v1/index.js +860 -0
- package/lib/integrations/relay-v2/index.d.ts +145 -0
- package/lib/integrations/relay-v2/index.js +752 -0
- package/lib/integrations/relay-v2/process.d.ts +32 -0
- package/lib/integrations/relay-v2/process.js +112 -0
- package/lib/integrations/relay-v2/protocol.d.ts +172 -0
- package/lib/integrations/relay-v2/protocol.js +142 -0
- package/lib/profiles/resolve.d.ts +5 -1
- package/lib/profiles/resolve.js +31 -0
- package/lib/storage/document.d.ts +116 -16
- package/lib/toolchains/index.d.ts +9 -0
- package/lib/toolchains/index.js +9 -0
- package/lib/toolchains/inspect.d.ts +105 -0
- package/lib/toolchains/inspect.js +831 -0
- package/lib/toolchains/manifests.d.ts +67 -0
- package/lib/toolchains/manifests.js +238 -0
- package/lib/toolchains/probe.d.ts +28 -0
- package/lib/toolchains/probe.js +54 -0
- package/lib/toolchains/recipes.d.ts +33 -0
- package/lib/toolchains/recipes.js +51 -0
- package/lib/verification/acceptance.d.ts +16 -0
- package/lib/verification/acceptance.js +16 -0
- package/lib/verification/gates.d.ts +38 -0
- package/lib/verification/gates.js +108 -26
- package/package.json +22 -1
- package/profiles/go-gotestsum.json +21 -0
- package/profiles/npm-ci.json +22 -0
- package/profiles/pnpm-frozen.json +24 -0
- package/profiles/python-uv.json +26 -0
- package/schemas/execution-document.schema.json +345 -4
- package/schemas/execution-policy.schema.json +48 -0
- package/schemas/result-envelope.schema.json +213 -1
- package/schemas/run-event.schema.json +93 -0
- package/schemas/run-result.schema.json +207 -0
- package/schemas/stack-profile.schema.json +43 -0
- package/templates/controller-mode/AGENTS.controller.md +58 -0
- package/templates/controller-mode/README.md +44 -0
|
@@ -50,6 +50,26 @@ const entities = (text) => text.replace(/&(quot|apos|lt|gt|amp);/g, (_m, name) =
|
|
|
50
50
|
* Изменённое так имя не совпадёт с названным критерием, и подтверждения
|
|
51
51
|
* не будет: для секрета в имени теста это правильный исход.
|
|
52
52
|
*/
|
|
53
|
+
/**
|
|
54
|
+
* Значение атрибута XML: парсер работает без обработки сущностей (DOCTYPE
|
|
55
|
+
* запрещён, внешних сущностей нет), поэтому предопределённые сущности и
|
|
56
|
+
* числовые ссылки раскрываются здесь — один раз, без рекурсии. Иначе testcase
|
|
57
|
+
* `a["b"]` сохранялся как `a["b"]` и не совпадал с названным
|
|
58
|
+
* testId требования или критерия. Репортер JUnit Node.js экранирует `"`
|
|
59
|
+
* дважды (`"`); оставшийся после раскрытия `"` поэтому тоже
|
|
60
|
+
* читается как `"`.
|
|
61
|
+
*/
|
|
62
|
+
export function xmlAttribute(value) {
|
|
63
|
+
const named = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" };
|
|
64
|
+
return value
|
|
65
|
+
.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|amp|lt|gt|quot|apos);/g, (all, ref) => {
|
|
66
|
+
if (!ref.startsWith('#'))
|
|
67
|
+
return named[ref] ?? all;
|
|
68
|
+
const code = ref.startsWith('#x') ? parseInt(ref.slice(2), 16) : parseInt(ref.slice(1), 10);
|
|
69
|
+
return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : all;
|
|
70
|
+
})
|
|
71
|
+
.replace(/"/g, '"');
|
|
72
|
+
}
|
|
53
73
|
export function junitSummary(xml, redact = (value) => value) {
|
|
54
74
|
if (xml.includes('<!DOCTYPE') || XMLValidator.validate(xml) !== true)
|
|
55
75
|
throw new Error('Некорректный JUnit XML');
|
|
@@ -75,7 +95,10 @@ export function junitSummary(xml, redact = (value) => value) {
|
|
|
75
95
|
summary.tests++;
|
|
76
96
|
if (value && typeof value === 'object') {
|
|
77
97
|
const named = value;
|
|
78
|
-
const raw = [named['@_classname'], named['@_name']]
|
|
98
|
+
const raw = [named['@_classname'], named['@_name']]
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.map((v) => xmlAttribute(String(v)))
|
|
101
|
+
.join('.');
|
|
79
102
|
const redacted = redact(raw);
|
|
80
103
|
// Длинное имя заменяется хешем: обрезка дала бы коллизии и ложные
|
|
81
104
|
// совпадения, а без замены receipt не перенёс бы манифест.
|
|
@@ -228,7 +251,14 @@ export async function prepareReportPath(root, path, escape) {
|
|
|
228
251
|
export async function runCheck(
|
|
229
252
|
// Нужна только политика изоляции: проверки ревьюера исполняются вне сервера.
|
|
230
253
|
h, options) {
|
|
231
|
-
let
|
|
254
|
+
let launched = false;
|
|
255
|
+
const confirm = () => {
|
|
256
|
+
if (launched)
|
|
257
|
+
return;
|
|
258
|
+
launched = true;
|
|
259
|
+
options.onStart?.();
|
|
260
|
+
};
|
|
261
|
+
let argv = options.argv, env = options.env, started, result, attempts = 1;
|
|
232
262
|
const scratch = await realpath(await mkdtemp(join(tmpdir(), 'dc-gate-')));
|
|
233
263
|
// Метка старта — вне TMPDIR команды: проверка, чистящая свой временный
|
|
234
264
|
// каталог, не должна стирать признак того, что она уже начиналась.
|
|
@@ -252,39 +282,73 @@ h, options) {
|
|
|
252
282
|
argv = wrapped.argv;
|
|
253
283
|
env = { ...env, ...wrapped.env };
|
|
254
284
|
}
|
|
255
|
-
const execute = () =>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
const execute = () => {
|
|
286
|
+
// Уже отменённый вызов не запускается: слушатель отмены на отменённом
|
|
287
|
+
// сигнале не срабатывает, и команда шла бы до таймаута.
|
|
288
|
+
if (options.signal.aborted)
|
|
289
|
+
throw Object.assign(new Error('Проверка отменена до запуска'), { name: 'AbortError' });
|
|
290
|
+
return command(argv, options.cwd, {
|
|
291
|
+
signal: options.signal,
|
|
292
|
+
...(started === undefined ? { onSpawn: confirm } : {}),
|
|
293
|
+
timeoutMs: options.timeoutMs,
|
|
294
|
+
env,
|
|
295
|
+
redact: options.redact,
|
|
296
|
+
// Отсоединившийся потомок проверки не переживает её: и при успехе,
|
|
297
|
+
// и по таймауту, отмене или падению.
|
|
298
|
+
// Каталоги проверки принадлежат только ей: процесс, запущенный в них
|
|
299
|
+
// во время проверки, — её потомок, даже без метки и родства.
|
|
300
|
+
contain: { dirs: [...options.write, scratch, marks] },
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
try {
|
|
272
304
|
result = await execute();
|
|
305
|
+
// sandbox-runtime ищет shell через `which` с таймаутом в секунду и под
|
|
306
|
+
// нагрузкой отказывает до запуска команды. Повтор допустим, только если
|
|
307
|
+
// команда точно не начиналась: метки старта нет.
|
|
308
|
+
for (; attempts < 3 && sandboxNotStarted(result, started); attempts++) {
|
|
309
|
+
await new Promise((r) => setTimeout(r, 500 * attempts));
|
|
310
|
+
result = await execute();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
// Факт старта читается до удаления метки — и при исключении после него.
|
|
315
|
+
if (started !== undefined && existsSync(started))
|
|
316
|
+
confirm();
|
|
273
317
|
}
|
|
274
|
-
|
|
318
|
+
// Без песочницы завершившийся процесс точно порождался.
|
|
319
|
+
if (started === undefined)
|
|
320
|
+
confirm();
|
|
321
|
+
return { ...result, attempts, started: launched };
|
|
275
322
|
}
|
|
276
323
|
finally {
|
|
277
324
|
// Каждый каталог убирается независимо: отказ уборки временного каталога
|
|
278
325
|
// проверки (она могла снять с него права) не должен оставлять и метки.
|
|
279
326
|
const removed = await Promise.allSettled([
|
|
280
|
-
|
|
281
|
-
|
|
327
|
+
checkCleanup.remove(scratch),
|
|
328
|
+
checkCleanup.remove(marks),
|
|
282
329
|
]);
|
|
283
330
|
const failed = removed.find((r) => r.status === 'rejected');
|
|
284
|
-
if (failed)
|
|
285
|
-
|
|
331
|
+
if (failed) {
|
|
332
|
+
// Отказ уборки после завершения не стирает исход проверки: код и
|
|
333
|
+
// вывод доступны вызывающему через ошибку (`completedCheck`).
|
|
334
|
+
const error = failed.reason instanceof Error ? failed.reason : new Error(String(failed.reason));
|
|
335
|
+
throw result
|
|
336
|
+
? Object.assign(error, { [checkResult]: { ...result, attempts, started: launched } })
|
|
337
|
+
: error;
|
|
338
|
+
}
|
|
286
339
|
}
|
|
287
340
|
}
|
|
341
|
+
const checkResult = Symbol('runCheck.result');
|
|
342
|
+
/** Исход проверки, завершившейся до исключения (например, отказа уборки), если он есть. */
|
|
343
|
+
export function completedCheck(error) {
|
|
344
|
+
return error && typeof error === 'object' && checkResult in error
|
|
345
|
+
? error[checkResult]
|
|
346
|
+
: undefined;
|
|
347
|
+
}
|
|
348
|
+
/** Уборка каталогов проверки; тест подменяет её, чтобы проверить отказ после исхода. */
|
|
349
|
+
export const checkCleanup = {
|
|
350
|
+
remove: (dir) => rm(dir, { recursive: true, force: true }),
|
|
351
|
+
};
|
|
288
352
|
/** Проверка механизма песочницы; тест подменяет её, чтобы проверить отказ. */
|
|
289
353
|
export const isolationBackend = { check: isolationSupport };
|
|
290
354
|
/**
|
|
@@ -305,6 +369,16 @@ export function sandboxBlockedHosts(output, allowed) {
|
|
|
305
369
|
return [...hosts].filter((host) => host !== '127.0.0.1' &&
|
|
306
370
|
!allowed.some((d) => host === d.toLowerCase() || host.endsWith('.' + d.toLowerCase())));
|
|
307
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Окружение свежести test gate: те же переменные у gate движка и у gate,
|
|
374
|
+
* запущенного проверкой reviewer, — иначе probe мог бы прочитать cache там,
|
|
375
|
+
* где gate исполняет тесты заново.
|
|
376
|
+
*/
|
|
377
|
+
export function freshnessEnv(kind, env) {
|
|
378
|
+
return kind === 'test'
|
|
379
|
+
? { TURBO_FORCE: 'true', GOFLAGS: [env.GOFLAGS, '-count=1'].filter(Boolean).join(' ') }
|
|
380
|
+
: {};
|
|
381
|
+
}
|
|
308
382
|
/**
|
|
309
383
|
* Gate попытки. `test` принимается только по свежему (прежний отчёт удалён до
|
|
310
384
|
* запуска) непустому JUnit без failures, errors и skips на этом SHA; exit 0
|
|
@@ -356,14 +430,22 @@ export async function runGate(run) {
|
|
|
356
430
|
...(corpusPath ? { DEVCONTOUR_CORPUS: corpusPath } : {}),
|
|
357
431
|
}
|
|
358
432
|
: {};
|
|
433
|
+
// Test gate исполняет тесты заново, через какую бы обёртку (package
|
|
434
|
+
// script, task runner) ни шла команда: Turbo не читает cache (`TURBO_FORCE`),
|
|
435
|
+
// Go не берёт результат из кэша тестов (`-count=1` в GOFLAGS, последним).
|
|
436
|
+
// Восстановленный из cache зелёный отчёт иначе выглядел бы свежим.
|
|
437
|
+
const freshness = freshnessEnv(gate.kind, run.execution.env);
|
|
359
438
|
let kind = 'gate';
|
|
360
|
-
|
|
439
|
+
// Заголовок лога — фактические каталог и argv запуска: digest лога входит в
|
|
440
|
+
// evidence, и запись называет, где и чем проверка исполнялась.
|
|
441
|
+
let passed = false, summary = '', log = `# harness gate ${gate.id}: cwd=${relative(root, gateCwd) || '.'} argv=${JSON.stringify(gate.command.map(redact))}${gate.kind === 'test' ? ` fresh=${redact(JSON.stringify(freshness))}` : ''}\n`, exitCode = -1, manifest;
|
|
361
442
|
try {
|
|
362
443
|
const result = await runCheck({ config: run.policy }, {
|
|
363
444
|
argv: gate.command,
|
|
364
445
|
cwd: gateCwd,
|
|
365
446
|
env: {
|
|
366
447
|
...run.execution.env,
|
|
448
|
+
...freshness,
|
|
367
449
|
...(reportPath
|
|
368
450
|
? { DEVCONTOUR_REPORT_PATH: reportPath, HARNESS_REPORT_PATH: reportPath }
|
|
369
451
|
: {}),
|
|
@@ -379,7 +461,7 @@ export async function runGate(run) {
|
|
|
379
461
|
settingsDir: join(artifactDir, gate.id),
|
|
380
462
|
});
|
|
381
463
|
exitCode = result.code;
|
|
382
|
-
log
|
|
464
|
+
log += result.stdout + '\n' + result.stderr;
|
|
383
465
|
if (result.timedOut || signal.aborted)
|
|
384
466
|
throw new Error('Проверка прервана или превысила timeout');
|
|
385
467
|
if (result.code !== 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devcontour-harness",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
4
|
"description": "Headless execution harness: work graph, isolated attempts, gates, independent reviews and accepted results.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,26 @@
|
|
|
32
32
|
"./testing": {
|
|
33
33
|
"types": "./lib/testing/index.d.ts",
|
|
34
34
|
"default": "./lib/testing/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./toolchains": {
|
|
37
|
+
"types": "./lib/toolchains/index.d.ts",
|
|
38
|
+
"default": "./lib/toolchains/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./integrations/connector": {
|
|
41
|
+
"types": "./lib/integrations/connector/index.d.ts",
|
|
42
|
+
"default": "./lib/integrations/connector/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./integrations/relay-v1": {
|
|
45
|
+
"types": "./lib/integrations/relay-v1/index.d.ts",
|
|
46
|
+
"default": "./lib/integrations/relay-v1/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./integrations/relay-v2": {
|
|
49
|
+
"types": "./lib/integrations/relay-v2/index.d.ts",
|
|
50
|
+
"default": "./lib/integrations/relay-v2/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./integrations/memory": {
|
|
53
|
+
"types": "./lib/integrations/memory/index.d.ts",
|
|
54
|
+
"default": "./lib/integrations/memory/index.js"
|
|
35
55
|
}
|
|
36
56
|
},
|
|
37
57
|
"files": [
|
|
@@ -58,6 +78,7 @@
|
|
|
58
78
|
"schemas:check": "node --import tsx scripts/build-schemas.ts --check",
|
|
59
79
|
"lint": "eslint .",
|
|
60
80
|
"test": "node scripts/test.mjs",
|
|
81
|
+
"test:integration": "node --import tsx --test --test-concurrency=1 --test-timeout=1800000 tests/integration/*.test.ts",
|
|
61
82
|
"test:package": "npm run build && node scripts/smoke-package.mjs",
|
|
62
83
|
"check": "npm run lint && npm run typecheck && npm run schemas:check && npm run build && npm test && node scripts/smoke-package.mjs",
|
|
63
84
|
"format": "prettier --write .",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "go-gotestsum",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"capabilities": ["go", "unit-tests"],
|
|
5
|
+
"protectedPaths": ["go.mod", "go.sum"],
|
|
6
|
+
"toolchain": {
|
|
7
|
+
"tools": { "gotestsum": "1.12.3" }
|
|
8
|
+
},
|
|
9
|
+
"prepare": [{ "id": "install", "command": ["go", "mod", "download"], "timeoutMs": 600000 }],
|
|
10
|
+
"gates": [
|
|
11
|
+
{ "id": "vet", "kind": "check", "command": ["go", "vet", "./..."], "timeoutMs": 240000 },
|
|
12
|
+
{
|
|
13
|
+
"id": "acceptance",
|
|
14
|
+
"kind": "test",
|
|
15
|
+
"command": ["gotestsum", "--junitfile", ".reports/junit.xml", "--", "-count=1", "./..."],
|
|
16
|
+
"timeoutMs": 240000,
|
|
17
|
+
"dependsOn": ["vet"],
|
|
18
|
+
"report": { "type": "junit", "path": ".reports/junit.xml" }
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "npm-ci",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"capabilities": ["node", "npm", "unit-tests"],
|
|
5
|
+
"protectedPaths": ["package.json", "package-lock.json"],
|
|
6
|
+
"toolchain": {
|
|
7
|
+
"tools": { "npm": "11.4.2" },
|
|
8
|
+
"packageManagers": { ".": "npm" }
|
|
9
|
+
},
|
|
10
|
+
"prepare": [{ "id": "install", "command": ["npm", "ci"], "timeoutMs": 600000 }],
|
|
11
|
+
"gates": [
|
|
12
|
+
{ "id": "check", "kind": "check", "command": ["npm", "run", "check"], "timeoutMs": 240000 },
|
|
13
|
+
{
|
|
14
|
+
"id": "unit-tests",
|
|
15
|
+
"kind": "test",
|
|
16
|
+
"command": ["npm", "run", "test"],
|
|
17
|
+
"timeoutMs": 240000,
|
|
18
|
+
"dependsOn": ["check"],
|
|
19
|
+
"report": { "type": "junit", "path": ".reports/junit.xml" }
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "pnpm-frozen",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"capabilities": ["node", "pnpm", "unit-tests"],
|
|
5
|
+
"protectedPaths": ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml"],
|
|
6
|
+
"toolchain": {
|
|
7
|
+
"tools": { "pnpm": "10.12.3" },
|
|
8
|
+
"packageManagers": { ".": "pnpm" }
|
|
9
|
+
},
|
|
10
|
+
"prepare": [
|
|
11
|
+
{ "id": "install", "command": ["pnpm", "install", "--frozen-lockfile"], "timeoutMs": 600000 }
|
|
12
|
+
],
|
|
13
|
+
"gates": [
|
|
14
|
+
{ "id": "check", "kind": "check", "command": ["pnpm", "run", "check"], "timeoutMs": 240000 },
|
|
15
|
+
{
|
|
16
|
+
"id": "unit-tests",
|
|
17
|
+
"kind": "test",
|
|
18
|
+
"command": ["pnpm", "run", "test"],
|
|
19
|
+
"timeoutMs": 240000,
|
|
20
|
+
"dependsOn": ["check"],
|
|
21
|
+
"report": { "type": "junit", "path": ".reports/junit.xml" }
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "python-uv",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"capabilities": ["python", "uv", "pytest"],
|
|
5
|
+
"protectedPaths": ["pyproject.toml", "uv.lock", ".python-version"],
|
|
6
|
+
"toolchain": {
|
|
7
|
+
"tools": { "uv": "0.11.28" }
|
|
8
|
+
},
|
|
9
|
+
"prepare": [{ "id": "install", "command": ["uv", "sync", "--locked"], "timeoutMs": 600000 }],
|
|
10
|
+
"gates": [
|
|
11
|
+
{
|
|
12
|
+
"id": "lint",
|
|
13
|
+
"kind": "check",
|
|
14
|
+
"command": ["uv", "run", "--no-sync", "ruff", "check", "."],
|
|
15
|
+
"timeoutMs": 240000
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "acceptance",
|
|
19
|
+
"kind": "test",
|
|
20
|
+
"command": ["uv", "run", "--no-sync", "pytest", "--junitxml=.reports/junit.xml"],
|
|
21
|
+
"timeoutMs": 240000,
|
|
22
|
+
"dependsOn": ["lint"],
|
|
23
|
+
"report": { "type": "junit", "path": ".reports/junit.xml" }
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|