devcontour-harness 0.1.0-alpha.13 → 0.1.0-alpha.16

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.
@@ -31,6 +31,57 @@ export const strongPromises = new Set([
31
31
  'completeness',
32
32
  'performance',
33
33
  ]);
34
+ /**
35
+ * Проверка по закреплённым эталонным наборам (`verification: 'reference'`).
36
+ *
37
+ * Ответы независимого эталона (решатель, полный перебор, таблица DP)
38
+ * получены заранее, сверены с другим способом на семействах входов и
39
+ * закреплены артефактами контракта. Gate сверяет реализацию с наборами и
40
+ * отчитывается тем же отчётом свойств: число проверенных входов по свойству
41
+ * и по семействам. Каждый набор принадлежит одному gate: его минимум и
42
+ * минимумы семейств — доля этого gate в покрытии обязательства. Обобщения за
43
+ * пределы наборов такой способ не доказывает — это называет `gaps`.
44
+ */
45
+ export const referenceSchema = z.strictObject({
46
+ /** Эталон и его пределы: версия, предел времени, что исключено из наборов. */
47
+ oracle: z.strictObject({ description: text(), limits: text() }),
48
+ /** Сверка эталона с другим способом: семейство, способ, совпало из всего. */
49
+ crossChecks: z
50
+ .array(z.strictObject({
51
+ family: text(200),
52
+ against: text(500),
53
+ matches: z.number().int().min(0).max(100_000_000),
54
+ total: z.number().int().min(1).max(100_000_000),
55
+ /**
56
+ * Закреплённые файлы, воспроизводящие сверку (скрипт, журнал) — среди
57
+ * артефактов контракта. Без них числа — утверждение автора.
58
+ */
59
+ artifacts: z.array(relativePathSchema).min(1).max(10).optional(),
60
+ }))
61
+ .min(1)
62
+ .max(10),
63
+ sets: z
64
+ .array(z.strictObject({
65
+ /** Gate, который сверяет реализацию с этим набором. */
66
+ gate: idSchema,
67
+ /** Файлы набора — среди артефактов контракта. */
68
+ artifacts: z.array(relativePathSchema).min(1).max(10),
69
+ /** Сколько входов набора gate обязан проверить за прогон. */
70
+ minCases: z.number().int().min(1).max(1_000_000),
71
+ /** Доля семейств покрытия обязательства в этом наборе. */
72
+ families: z
73
+ .array(z.strictObject({
74
+ id: familyIdSchema,
75
+ minCases: z.number().int().min(1).max(1_000_000),
76
+ }))
77
+ .max(20)
78
+ .default([]),
79
+ }))
80
+ .min(1)
81
+ .max(20),
82
+ /** Чего наборы не доказывают: обобщение за пределы входов, классы вне наборов. */
83
+ gaps: text(),
84
+ });
34
85
  export const obligationSchema = z.strictObject({
35
86
  property: propertyIdSchema,
36
87
  /** Существующие требования или критерии приёмки, которые обязательство уточняет. */
@@ -79,6 +130,15 @@ export const obligationSchema = z.strictObject({
79
130
  oracle: text(),
80
131
  benchmark: z.strictObject({ method: text(), environment: text() }).optional(),
81
132
  }),
133
+ /**
134
+ * Gates, исполняющие обязательство; без поля — gate договора. У
135
+ * генеративного обязательства каждый из них проверяет полное покрытие, у
136
+ * эталонного — свои наборы.
137
+ */
138
+ gates: z.array(idSchema).min(1).max(10).optional(),
139
+ /** Способ исполнения: `property` (без поля) — генератор; `reference` — эталонные наборы. */
140
+ verification: z.enum(['property', 'reference']).optional(),
141
+ reference: referenceSchema.optional(),
82
142
  /** Тяжёлый точный алгоритм или жёсткое время: до реализации нужен ограниченный эксперимент. */
83
143
  hard: z.boolean().default(false),
84
144
  feasibility: z
@@ -90,7 +150,10 @@ export const obligationSchema = z.strictObject({
90
150
  });
91
151
  export const checkDeclarationSchema = z.strictObject({
92
152
  version: z.literal(1),
93
- /** Gate, который исполняет проверку (генеративный, с закреплённым checker). */
153
+ /**
154
+ * Gate проверки по умолчанию: исполняет обязательства без своих `gates`
155
+ * (генеративный, с закреплённым checker).
156
+ */
94
157
  gate: idSchema,
95
158
  obligations: z.array(obligationSchema).min(1).max(30),
96
159
  /**
@@ -2289,6 +2289,34 @@ export declare const localSchemas: {
2289
2289
  environment: z.ZodString;
2290
2290
  }, z.core.$strict>>;
2291
2291
  }, z.core.$strict>;
2292
+ gates: z.ZodOptional<z.ZodArray<z.ZodString>>;
2293
+ verification: z.ZodOptional<z.ZodEnum<{
2294
+ property: "property";
2295
+ reference: "reference";
2296
+ }>>;
2297
+ reference: z.ZodOptional<z.ZodObject<{
2298
+ oracle: z.ZodObject<{
2299
+ description: z.ZodString;
2300
+ limits: z.ZodString;
2301
+ }, z.core.$strict>;
2302
+ crossChecks: z.ZodArray<z.ZodObject<{
2303
+ family: z.ZodString;
2304
+ against: z.ZodString;
2305
+ matches: z.ZodNumber;
2306
+ total: z.ZodNumber;
2307
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodString>>;
2308
+ }, z.core.$strict>>;
2309
+ sets: z.ZodArray<z.ZodObject<{
2310
+ gate: z.ZodString;
2311
+ artifacts: z.ZodArray<z.ZodString>;
2312
+ minCases: z.ZodNumber;
2313
+ families: z.ZodDefault<z.ZodArray<z.ZodObject<{
2314
+ id: z.ZodString;
2315
+ minCases: z.ZodNumber;
2316
+ }, z.core.$strict>>>;
2317
+ }, z.core.$strict>>;
2318
+ gaps: z.ZodString;
2319
+ }, z.core.$strict>>;
2292
2320
  hard: z.ZodDefault<z.ZodBoolean>;
2293
2321
  feasibility: z.ZodOptional<z.ZodObject<{
2294
2322
  spike: z.ZodString;
@@ -760,6 +760,34 @@ export declare const publishedSchemas: {
760
760
  environment: z.ZodString;
761
761
  }, z.core.$strict>>;
762
762
  }, z.core.$strict>;
763
+ gates: z.ZodOptional<z.ZodArray<z.ZodString>>;
764
+ verification: z.ZodOptional<z.ZodEnum<{
765
+ property: "property";
766
+ reference: "reference";
767
+ }>>;
768
+ reference: z.ZodOptional<z.ZodObject<{
769
+ oracle: z.ZodObject<{
770
+ description: z.ZodString;
771
+ limits: z.ZodString;
772
+ }, z.core.$strict>;
773
+ crossChecks: z.ZodArray<z.ZodObject<{
774
+ family: z.ZodString;
775
+ against: z.ZodString;
776
+ matches: z.ZodNumber;
777
+ total: z.ZodNumber;
778
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodString>>;
779
+ }, z.core.$strict>>;
780
+ sets: z.ZodArray<z.ZodObject<{
781
+ gate: z.ZodString;
782
+ artifacts: z.ZodArray<z.ZodString>;
783
+ minCases: z.ZodNumber;
784
+ families: z.ZodDefault<z.ZodArray<z.ZodObject<{
785
+ id: z.ZodString;
786
+ minCases: z.ZodNumber;
787
+ }, z.core.$strict>>>;
788
+ }, z.core.$strict>>;
789
+ gaps: z.ZodString;
790
+ }, z.core.$strict>>;
763
791
  hard: z.ZodDefault<z.ZodBoolean>;
764
792
  feasibility: z.ZodOptional<z.ZodObject<{
765
793
  spike: z.ZodString;
@@ -18,7 +18,7 @@ import { policySourceChanged } from "./provenance.js";
18
18
  import { JointRunner } from "../engine/joint-runner.js";
19
19
  import { git } from "../environment/process.js";
20
20
  import { analyzeRecovery, applyCases } from "./recovery.js";
21
- import { declarationProblems } from "../verification/checks.js";
21
+ import { declarationProblems, referenceNote } from "../verification/checks.js";
22
22
  import { spikeOf, spikesOf } from "../verification/spike.js";
23
23
  import { staleArtifacts } from "../verification/confirm.js";
24
24
  const stateFile = (dir) => join(dir, 'state.sqlite');
@@ -193,6 +193,7 @@ export class Harness {
193
193
  : 'You independently review a contract document. Check that every obligation is feasible and measurable. Do not edit files.',
194
194
  JSON.stringify(input.kind === 'plan' ? input.graph : input.contract, null, 2),
195
195
  input.kind === 'contract' ? this.spikeNote(input.contract) : '',
196
+ input.kind === 'contract' ? referenceNote(input.contract.checks) : '',
196
197
  ],
197
198
  ...(context ? { context } : {}),
198
199
  ...(input.kind === 'contract' ? { contract: input.contract } : {}),
@@ -139,6 +139,31 @@ export declare function recoveryBrief(view: ExecutionView, work: WorkState, poli
139
139
  };
140
140
  hard: boolean;
141
141
  explanation?: string | undefined;
142
+ gates?: string[] | undefined;
143
+ verification?: "property" | "reference" | undefined;
144
+ reference?: {
145
+ oracle: {
146
+ description: string;
147
+ limits: string;
148
+ };
149
+ crossChecks: {
150
+ family: string;
151
+ against: string;
152
+ matches: number;
153
+ total: number;
154
+ artifacts?: string[] | undefined;
155
+ }[];
156
+ sets: {
157
+ gate: string;
158
+ artifacts: string[];
159
+ minCases: number;
160
+ families: {
161
+ id: string;
162
+ minCases: number;
163
+ }[];
164
+ }[];
165
+ gaps: string;
166
+ } | undefined;
142
167
  feasibility?: {
143
168
  spike: string;
144
169
  digest: string;
@@ -19,11 +19,11 @@ import { REQUIREMENT_GATE, assertRequirements } from "../verification/requiremen
19
19
  import { BlockedError, classifyFailure } from "./classify.js";
20
20
  import { TaskFailure } from "./failure.js";
21
21
  import { prompt } from "./prompt.js";
22
- import { contractDocuments, orderCheckManifest } from "../verification/checks.js";
22
+ import { contractDocuments, orderCheckManifest, referenceCodeNote, referenceSetFiles, setFileReference, } from "../verification/checks.js";
23
23
  import { CorpusStore, localRoots } from "../verification/corpus.js";
24
24
  import { submitCase } from "../verification/confirm.js";
25
25
  import { runSpike } from "../verification/spike.js";
26
- import { propertySeed } from "../verification/property.js";
26
+ import { expectsReference, propertySeed } from "../verification/property.js";
27
27
  import { selfCheckNote, writeSelfCheck } from "../verification/self-check.js";
28
28
  import { measuredInvoke } from "./measured.js";
29
29
  import { continuableDraft, implementationBasis } from "./reuse.js";
@@ -491,6 +491,24 @@ export class Runner {
491
491
  const forbidden = changed.filter((p) => within(p, protectedList));
492
492
  if (forbidden.length)
493
493
  throw new TaskFailure('scope-violation', `Изменены защищённые файлы: ${forbidden.join(', ')}`);
494
+ // Ответы эталонных наборов лежат в рабочем дереве: изменённый код,
495
+ // который называет файл набора строковым литералом, читает эталон, а не
496
+ // решает. Тесты исполнителя (не артефакты контракта) проверяются так же:
497
+ // через них ответы тоже можно подать реализации.
498
+ const sets = this.engine.read((v) => referenceSetFiles(contractDocuments(work.order, v), repo.id));
499
+ if (sets.length) {
500
+ const named = [];
501
+ for (const path of changed) {
502
+ const text = await git(cwd, 'show', `${range ? range[1] : ''}:${path}`).catch(() => '');
503
+ if (text.length > 4 * 1024 * 1024)
504
+ continue;
505
+ const hit = setFileReference(path, text, sets);
506
+ if (hit)
507
+ named.push(`${path} → ${hit}`);
508
+ }
509
+ if (named.length)
510
+ throw new TaskFailure('scope-violation', `Реализация ссылается на файлы эталонных наборов: ${named.join(', ')}`);
511
+ }
494
512
  if (!changed.length)
495
513
  throw new TaskFailure('empty-result', 'Изменений нет: поручение отчиталось о выполнении пустым коммитом');
496
514
  const role = roleBinding(this.policy, work.order.role, work.order.repositoryId)?.writePaths;
@@ -990,6 +1008,10 @@ export class Runner {
990
1008
  await this.timed(attempt, stage, () => this.runOneGate(attempt, cwd, sha, phase, gate, artifacts, signal, execution, consumer));
991
1009
  }
992
1010
  async runOneGate(attempt, cwd, sha, phase, gate, artifacts, signal, execution, consumer) {
1011
+ // Генеративный gate — всегда; gate эталонных наборов — когда манифест
1012
+ // поручения ждёт от него отчёта (без договора — прежний уровень).
1013
+ const run = this.propertyRun(attempt, gate, consumer);
1014
+ const property = gate.property || expectsReference(run.expected) ? run : undefined;
993
1015
  await runGate({
994
1016
  policy: this.policy,
995
1017
  record: (e) => this.engine.evidence(attempt.id, attempt.token, e),
@@ -1004,7 +1026,7 @@ export class Runner {
1004
1026
  execution,
1005
1027
  boundary: this.boundary(consumer?.dependencies ?? attempt.dependencies),
1006
1028
  ...(consumer ? { consumer: consumer.repositoryId } : {}),
1007
- ...(gate.property ? { property: this.propertyRun(attempt, gate, consumer) } : {}),
1029
+ ...(property ? { property } : {}),
1008
1030
  });
1009
1031
  }
1010
1032
  /**
@@ -1602,6 +1624,9 @@ export class Runner {
1602
1624
  }))));
1603
1625
  // Согласованные контракты поручения — их текстом, по закреплённому digest.
1604
1626
  const contracts = contractDocuments(v.work(attempt.workId).order, v);
1627
+ const sets = referenceCodeNote(referenceSetFiles(contracts), review);
1628
+ if (sets)
1629
+ out.push(sets);
1605
1630
  if (contracts.length)
1606
1631
  out.push('Approved contracts: ' +
1607
1632
  JSON.stringify(contracts.map((c) => ({ id: c.id, title: c.title, content: c.content }))));
package/lib/index.d.ts CHANGED
@@ -31,8 +31,8 @@ export { callDurationMs, callRecords, usageTotals } from './engine/usage.ts';
31
31
  * ожидаемая проверка поручения, сверка отчёта свойств, закреплённые версии
32
32
  * корпуса, подтверждение примера эталоном и переносимая форма корпуса.
33
33
  */
34
- export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, orderCheckManifest, orderPropertyProblems, suiteBasis, } from './verification/checks.ts';
35
- export { corpusWitness, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, type CheckManifest, } from './verification/property.ts';
34
+ export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, isReference, obligationGates, orderCheckManifest, orderPropertyProblems, referenceCodeNote, referenceNote, referenceSetFiles, setFileReference, suiteBasis, } from './verification/checks.ts';
35
+ export { corpusWitness, expectsReference, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, type CheckManifest, } from './verification/property.ts';
36
36
  export { CorpusStore, canonicalJson, caseId, corpusFile, parseCanonical, versionOf, portableSuite, scrubLocalPaths, scrubPortable, verifySuite, type PortableSuite, } from './verification/corpus.ts';
37
37
  export { caseHooks, caseInputSchema, staleArtifacts, submitCase, type CaseRequest, type ConfirmContext, } from './verification/confirm.ts';
38
38
  export { assertResult, pinnedCorpus, propertyProofProblem, resultProblems, type ReceiverContext, type ReceiverPolicy, } from './verification/acceptance.ts';
package/lib/index.js CHANGED
@@ -31,8 +31,8 @@ export { callDurationMs, callRecords, usageTotals } from "./engine/usage.js";
31
31
  * ожидаемая проверка поручения, сверка отчёта свойств, закреплённые версии
32
32
  * корпуса, подтверждение примера эталоном и переносимая форма корпуса.
33
33
  */
34
- export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, orderCheckManifest, orderPropertyProblems, suiteBasis, } from "./verification/checks.js";
35
- export { corpusWitness, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, } from "./verification/property.js";
34
+ export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, isReference, obligationGates, orderCheckManifest, orderPropertyProblems, referenceCodeNote, referenceNote, referenceSetFiles, setFileReference, suiteBasis, } from "./verification/checks.js";
35
+ export { corpusWitness, expectsReference, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, } from "./verification/property.js";
36
36
  export { CorpusStore, canonicalJson, caseId, corpusFile, parseCanonical, versionOf, portableSuite, scrubLocalPaths, scrubPortable, verifySuite, } from "./verification/corpus.js";
37
37
  export { caseHooks, caseInputSchema, staleArtifacts, submitCase, } from "./verification/confirm.js";
38
38
  export { assertResult, pinnedCorpus, propertyProofProblem, resultProblems, } from "./verification/acceptance.js";
@@ -20,7 +20,8 @@ export function propertyGates(order, contracts) {
20
20
  const found = obligationOf(contracts, id);
21
21
  if (!found)
22
22
  return { problem: `свойство ${id} не объявлено в договорах поручения` };
23
- byGate.set(found.gate, [...(byGate.get(found.gate) ?? []), id]);
23
+ for (const gate of found.gates)
24
+ byGate.set(gate, [...(byGate.get(gate) ?? []), id]);
24
25
  }
25
26
  return { byGate };
26
27
  }
@@ -8,9 +8,30 @@ import type { CheckManifest } from './property.ts';
8
8
  export declare function contractDocuments(order: Pick<WorkOrder, 'contracts'>, view: ExecutionView): ContractDocument[];
9
9
  type Declared = {
10
10
  contract: ContractDocument;
11
+ /** Первый gate обязательства (у обязательства без `gates` — gate договора). */
11
12
  gate: string;
13
+ gates: string[];
12
14
  obligation: Obligation;
13
15
  };
16
+ /** Gates обязательства: свои `gates` или gate договора. */
17
+ export declare function obligationGates(declaration: Pick<CheckDeclaration, 'gate'>, o: Obligation): string[];
18
+ /** Обязательство проверяется закреплёнными эталонными наборами. */
19
+ export declare const isReference: (o: Obligation) => boolean;
20
+ /**
21
+ * Файлы эталонных наборов договоров поручения (в репозитории `repositoryId`).
22
+ * Ответы наборов лежат в рабочем дереве: реализация, которая читает их или
23
+ * повторяет их ответы, прошла бы каждый набор, ничего не решив.
24
+ */
25
+ export declare function referenceSetFiles(contracts: ContractDocument[], repositoryId?: string): string[];
26
+ /**
27
+ * Какой файл набора называет изменённый файл: имя файла набора (и, значит,
28
+ * его путь) целым токеном внутри строкового литерала. Комментарии и файлы без кода не
29
+ * считаются — упоминание набора в них ничего не читает. Имя, собранное во
30
+ * время исполнения, так не видно: это остаётся ревью кода.
31
+ */
32
+ export declare function setFileReference(path: string, text: string, sets: string[]): string | undefined;
33
+ /** Подсказка writer или reviewer кода о файлах эталонных наборов; пусто — их нет. */
34
+ export declare function referenceCodeNote(files: string[], review: boolean): string;
14
35
  /** Основание случаев корпуса: закреплённые файлы, gate и смысл обязательств. */
15
36
  export declare function suiteBasis(contract: ContractDocument): string;
16
37
  /**
@@ -38,6 +59,13 @@ export declare function declarationProblems(declaration: CheckDeclaration, conte
38
59
  digest: string;
39
60
  } | undefined;
40
61
  }): string[];
62
+ /**
63
+ * Подсказка reviewer контракта об эталонных обязательствах: что наборы
64
+ * доказывают, чего — нет (`gaps`), чем сверен эталон. Пустая строка — таких
65
+ * обязательств нет. Reviewer видит пределы способа рядом с самим договором,
66
+ * а не выводит их из длинного описания.
67
+ */
68
+ export declare function referenceNote(declaration: CheckDeclaration | undefined): string;
41
69
  /**
42
70
  * Свойства, объявленные поручением, существуют в его договорах и исполняются
43
71
  * его генеративными gates. Поручение без `properties` — прежний уровень.