devcontour-harness 0.1.0-alpha.11 → 0.1.0-alpha.13
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 +37 -5
- package/docs/architecture.md +6 -5
- package/docs/connectors.md +26 -0
- package/docs/quickstart.md +1 -1
- package/docs/release-notes/0.1.0-alpha.12.md +66 -0
- package/docs/release-notes/0.1.0-alpha.13.md +91 -0
- package/docs/standalone.md +25 -4
- package/docs/turbo-starter.md +2 -1
- package/docs/upgrade.md +14 -14
- package/examples/turbo-starter/setup.mjs +9 -2
- package/lib/connectors/files.js +2 -0
- package/lib/integrations/connector/commands.d.ts +11 -4
- package/lib/integrations/connector/commands.js +16 -2
- package/lib/integrations/connector/config.d.ts +5 -0
- package/lib/integrations/connector/config.js +18 -1
- package/lib/integrations/connector/index.d.ts +1 -1
- package/lib/integrations/connector/index.js +1 -1
- package/lib/integrations/connector/snapshot.d.ts +22 -1
- package/lib/integrations/connector/snapshot.js +60 -3
- package/lib/integrations/connector/source.d.ts +4 -0
- package/lib/integrations/connector/source.js +1 -0
- package/package.json +1 -1
- package/skills/harness/SKILL.md +17 -15
- package/skills/harness-setup/SKILL.md +478 -0
- package/templates/controller-mode/README.md +5 -0
|
@@ -40,6 +40,7 @@ export declare const taskMappingSchema: z.ZodObject<{
|
|
|
40
40
|
}>>;
|
|
41
41
|
}, z.core.$strict>>>;
|
|
42
42
|
requiredDocuments: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
43
|
+
context: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
43
44
|
}, z.core.$strict>;
|
|
44
45
|
export type TaskMapping = z.infer<typeof taskMappingSchema>;
|
|
45
46
|
export declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -51,6 +52,7 @@ export declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
51
52
|
projectId: z.ZodString;
|
|
52
53
|
actor: z.ZodString;
|
|
53
54
|
stateDir: z.ZodOptional<z.ZodString>;
|
|
55
|
+
contextDir: z.ZodOptional<z.ZodString>;
|
|
54
56
|
repository: z.ZodObject<{
|
|
55
57
|
id: z.ZodString;
|
|
56
58
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -72,6 +74,7 @@ export declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
72
74
|
}>>;
|
|
73
75
|
}, z.core.$strict>>>;
|
|
74
76
|
requiredDocuments: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
77
|
+
context: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
75
78
|
}, z.core.$strict>>;
|
|
76
79
|
completion: z.ZodDefault<z.ZodObject<{
|
|
77
80
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
@@ -122,6 +125,7 @@ export declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
122
125
|
projectId: z.ZodString;
|
|
123
126
|
actor: z.ZodString;
|
|
124
127
|
stateDir: z.ZodOptional<z.ZodString>;
|
|
128
|
+
contextDir: z.ZodOptional<z.ZodString>;
|
|
125
129
|
repository: z.ZodObject<{
|
|
126
130
|
id: z.ZodString;
|
|
127
131
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -143,6 +147,7 @@ export declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
143
147
|
}>>;
|
|
144
148
|
}, z.core.$strict>>>;
|
|
145
149
|
requiredDocuments: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
150
|
+
context: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
146
151
|
}, z.core.$strict>>;
|
|
147
152
|
completion: z.ZodDefault<z.ZodObject<{
|
|
148
153
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
@@ -57,6 +57,13 @@ export const taskMappingSchema = z.strictObject({
|
|
|
57
57
|
criteria: z.array(criterionMappingSchema).max(100).default([]),
|
|
58
58
|
/** Документы, без которых запуск блокируется (ID, ключ или имя). Остальные — дополнительные. */
|
|
59
59
|
requiredDocuments: z.array(z.string().min(1).max(200)).max(30).default([]),
|
|
60
|
+
/**
|
|
61
|
+
* Локальные пакеты контекста поручения — каталоги `<contextDir>/<id>` (например,
|
|
62
|
+
* оверлей controller-mode). Закрепляются в снимке вместе с контекстом задачи;
|
|
63
|
+
* изменение набора или содержимого пакетов — новый снимок и новое
|
|
64
|
+
* согласование; semantic digest задачи от них не зависит.
|
|
65
|
+
*/
|
|
66
|
+
context: z.array(idSchema).min(1).max(10).optional(),
|
|
60
67
|
});
|
|
61
68
|
const relaySettings = z.record(z.string(), z.unknown());
|
|
62
69
|
const common = {
|
|
@@ -69,6 +76,8 @@ const common = {
|
|
|
69
76
|
* для процессов, разделяющих этот каталог.
|
|
70
77
|
*/
|
|
71
78
|
stateDir: z.string().min(1).max(1000).optional(),
|
|
79
|
+
/** Каталог пакетов `tasks[].context` (относительно файла конфигурации); по умолчанию `work/context`. */
|
|
80
|
+
contextDir: z.string().min(1).max(1000).optional(),
|
|
72
81
|
repository: z.strictObject({
|
|
73
82
|
id: idSchema,
|
|
74
83
|
/** Путь репозитория для сверки с политикой (относительно файла конфигурации). */
|
|
@@ -161,6 +170,12 @@ export function parseConfig(value, baseDir, file) {
|
|
|
161
170
|
const dup = refs.find((r, i) => refs.indexOf(r) !== i);
|
|
162
171
|
if (dup)
|
|
163
172
|
throw new HarnessError('INVALID_INPUT', 'source: задача указана дважды: ' + dup);
|
|
173
|
+
// ID контекста задачи — `c` + 20 hex (contextIdOf): локальный пакет не может его занять.
|
|
174
|
+
const taken = parsed.data.tasks
|
|
175
|
+
.flatMap((t) => t.context ?? [])
|
|
176
|
+
.find((id) => /^c[0-9a-f]{20}$/.test(id));
|
|
177
|
+
if (taken)
|
|
178
|
+
throw new HarnessError('INVALID_INPUT', `source: пакет контекста ${taken} совпадает по форме с ID контекста задачи; выберите другое имя`);
|
|
164
179
|
return { config: parsed.data, baseDir, ...(file ? { file } : {}) };
|
|
165
180
|
}
|
|
166
181
|
export function loadConfig(path) {
|
|
@@ -204,7 +219,9 @@ export const contextIdOf = (orderId) => 'c' + orderId.slice(1);
|
|
|
204
219
|
export const graphIdOf = (provider, projectId, taskIds) => 'g' + short([provider, projectId, ...[...taskIds].sort()].join('\n'));
|
|
205
220
|
/** Digest сопоставления задачи: входит в semantic digest — смена mapping требует нового согласования. */
|
|
206
221
|
export const mappingDigest = (config, mapping) => digestOf({
|
|
207
|
-
|
|
222
|
+
// Пакеты контекста — не постановка задачи: их ID и digest входят в ревизию
|
|
223
|
+
// набора (новое согласование), но доставку принятого результата не меняют.
|
|
224
|
+
mapping: { ...mapping, ref: undefined, context: undefined },
|
|
208
225
|
repository: config.repository.id,
|
|
209
226
|
completion: config.completion,
|
|
210
227
|
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export * from './transport.ts';
|
|
7
7
|
export { connectorConfigSchema, criterionMappingSchema, taskMappingSchema, loadConfig, parseConfig, connectorIdOf, taskIdentity, parseIdentity, orderIdOf, graphIdOf, contextIdOf, mappingDigest, completionColumn, storeFileOf, type ConnectorConfig, type ConnectorConfigInput, type CriterionMapping, type TaskMapping, type LoadedConfig, } from './config.ts';
|
|
8
8
|
export { ConnectorStore, LeaseLost, type OperationRecord, type OperationStatus, type StepRecord, type StepStatus, type LeaseRecord, type ReceiptRecord, type SnapshotRecord, } from './store.ts';
|
|
9
|
-
export { buildSnapshot, checkIdentity, readStable, semanticDigest, takeSnapshot, assertEligible, type BuiltSnapshot, type SnapshotManifest, type SnapshotProblem, type TaskManifest, } from './snapshot.ts';
|
|
9
|
+
export { buildSnapshot, checkIdentity, readStable, semanticDigest, contextLimitIssues, localContext, takeSnapshot, assertEligible, type BuiltSnapshot, type SnapshotManifest, type SnapshotProblem, type TaskManifest, } from './snapshot.ts';
|
|
10
10
|
export { RelayWorkSource } from './source.ts';
|
|
11
11
|
export { RelaySink, operationKey, type ReconcileDecision, type SinkOptions } from './delivery.ts';
|
|
12
12
|
export { HistoryRecorder, type SessionLink } from './history.ts';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export * from "./transport.js";
|
|
7
7
|
export { connectorConfigSchema, criterionMappingSchema, taskMappingSchema, loadConfig, parseConfig, connectorIdOf, taskIdentity, parseIdentity, orderIdOf, graphIdOf, contextIdOf, mappingDigest, completionColumn, storeFileOf, } from "./config.js";
|
|
8
8
|
export { ConnectorStore, LeaseLost, } from "./store.js";
|
|
9
|
-
export { buildSnapshot, checkIdentity, readStable, semanticDigest, takeSnapshot, assertEligible, } from "./snapshot.js";
|
|
9
|
+
export { buildSnapshot, checkIdentity, readStable, semanticDigest, contextLimitIssues, localContext, takeSnapshot, assertEligible, } from "./snapshot.js";
|
|
10
10
|
export { RelayWorkSource } from "./source.js";
|
|
11
11
|
export { RelaySink, operationKey } from "./delivery.js";
|
|
12
12
|
export { HistoryRecorder } from "./history.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HarnessError, type ContextSnapshot, type WorkGraph } from '../../contracts/index.ts';
|
|
1
|
+
import { HarnessError, type ContextSnapshot, type ContextSourceEntry, type WorkGraph } from '../../contracts/index.ts';
|
|
2
2
|
import { type ConnectorConfig, type TaskMapping } from './config.ts';
|
|
3
3
|
import type { ReceiptRecord } from './store.ts';
|
|
4
4
|
import { type CriterionRecord, type RelayTransport, type TaskRead, type TransportIdentity } from './transport.ts';
|
|
@@ -84,6 +84,15 @@ export interface SnapshotManifest {
|
|
|
84
84
|
tasks: TaskManifest[];
|
|
85
85
|
truncated: string[];
|
|
86
86
|
problems: SnapshotProblem[];
|
|
87
|
+
/** Закреплённые локальные пакеты `tasks[].context`; без них поля нет. */
|
|
88
|
+
localContext?: LocalContextManifest[];
|
|
89
|
+
}
|
|
90
|
+
export interface LocalContextManifest {
|
|
91
|
+
id: string;
|
|
92
|
+
digest: string;
|
|
93
|
+
files: number;
|
|
94
|
+
references: number;
|
|
95
|
+
bytes: number;
|
|
87
96
|
}
|
|
88
97
|
export interface BuiltSnapshot {
|
|
89
98
|
graph: WorkGraph;
|
|
@@ -119,12 +128,24 @@ export declare function buildSnapshot(input: {
|
|
|
119
128
|
rounds: number;
|
|
120
129
|
/** Доверенный receipt этого коннектора для задачи-зависимости вне графа. */
|
|
121
130
|
receiptOf: (orderId: string) => ReceiptRecord | undefined;
|
|
131
|
+
/** Закреплённые локальные пакеты `tasks[].context` (localContext). */
|
|
132
|
+
local?: ContextSourceEntry[];
|
|
122
133
|
}): BuiltSnapshot;
|
|
134
|
+
/** Нарушения пределов снимка контекста: число источников, размер файла и сумма. */
|
|
135
|
+
export declare function contextLimitIssues(context: ContextSnapshot): string[];
|
|
136
|
+
/**
|
|
137
|
+
* Локальные пакеты `tasks[].context` из `contextDir`: те же правила, что у
|
|
138
|
+
* файлового контекста графа (каталог `<contextDir>/<id>`, `references/` —
|
|
139
|
+
* справки). Нет каталога или пакета — NOT_READY до чтения backend.
|
|
140
|
+
*/
|
|
141
|
+
export declare function localContext(config: ConnectorConfig, contextDir: string | undefined): Promise<ContextSourceEntry[]>;
|
|
123
142
|
/** Прочитать, собрать и проверить идентичность: общий путь `source-snapshot` и `run --source`. */
|
|
124
143
|
export declare function takeSnapshot(input: {
|
|
125
144
|
config: ConnectorConfig;
|
|
126
145
|
transport: RelayTransport;
|
|
127
146
|
receiptOf: (orderId: string) => ReceiptRecord | undefined;
|
|
147
|
+
/** Абсолютный каталог пакетов `tasks[].context`. */
|
|
148
|
+
contextDir?: string;
|
|
128
149
|
signal?: AbortSignal;
|
|
129
150
|
}): Promise<BuiltSnapshot>;
|
|
130
151
|
/** Отказ по eligibility/limits: причина и предел в сообщении, без запуска. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { API_VERSION, HarnessError, CONTEXT_LIMITS, contextDigest, contextIssues, digestOf, sha256, sourceDigest, workGraphDigest, workGraphSchema, } from "../../contracts/index.js";
|
|
2
3
|
import { connectorIdOf, contextIdOf, graphIdOf, mappingDigest, orderIdOf, taskIdentity, } from "./config.js";
|
|
3
4
|
import { TransportError, } from "./transport.js";
|
|
5
|
+
import { FileContextSource } from "../../connectors/files.js";
|
|
4
6
|
/** Безопасная причина отказа транспорта при чтении: сеть/CLI — не пустой список. */
|
|
5
7
|
export function readFailure(error, what) {
|
|
6
8
|
if (error instanceof HarnessError)
|
|
@@ -124,6 +126,8 @@ export function buildSnapshot(input) {
|
|
|
124
126
|
const missing = [];
|
|
125
127
|
const prerequisites = new Map();
|
|
126
128
|
const tasks = [];
|
|
129
|
+
/** Какие локальные пакеты у какой задачи (по порядку): часть ревизии набора. */
|
|
130
|
+
const packsByTask = [];
|
|
127
131
|
const truncated = [];
|
|
128
132
|
let totalBytes = 0;
|
|
129
133
|
const limits = config.limits;
|
|
@@ -331,8 +335,9 @@ export function buildSnapshot(input) {
|
|
|
331
335
|
...(mapping.gates ? { gates: mapping.gates } : {}),
|
|
332
336
|
requirements: [],
|
|
333
337
|
contracts: [],
|
|
334
|
-
context: [entry.id],
|
|
338
|
+
context: [entry.id, ...(mapping.context ?? [])],
|
|
335
339
|
});
|
|
340
|
+
packsByTask.push([identityOf, mapping.context ?? []]);
|
|
336
341
|
tasks.push({
|
|
337
342
|
ref: mapping.ref,
|
|
338
343
|
id: task.id,
|
|
@@ -367,6 +372,15 @@ export function buildSnapshot(input) {
|
|
|
367
372
|
connector,
|
|
368
373
|
tasks: tasks.map((t) => [t.identity, t.semanticDigest]),
|
|
369
374
|
prerequisites: prereqs,
|
|
375
|
+
// Без локальных пакетов ревизия та же, что до их появления. С ними —
|
|
376
|
+
// содержимое пакетов и их распределение по задачам: перенос пакета между
|
|
377
|
+
// задачами или смена порядка — тоже новое согласование.
|
|
378
|
+
...(input.local?.length || packsByTask.some(([, ids]) => ids.length)
|
|
379
|
+
? {
|
|
380
|
+
context: (input.local ?? []).map((s) => [s.id, s.digest]),
|
|
381
|
+
contextBy: [...packsByTask].sort(([a], [b]) => (a < b ? -1 : 1)),
|
|
382
|
+
}
|
|
383
|
+
: {}),
|
|
370
384
|
});
|
|
371
385
|
const joined = `${config.provider}:${config.projectId}:${taskIds.join('+')}`;
|
|
372
386
|
const graph = workGraphSchema.parse({
|
|
@@ -380,7 +394,15 @@ export function buildSnapshot(input) {
|
|
|
380
394
|
orders,
|
|
381
395
|
prerequisites: prereqs,
|
|
382
396
|
});
|
|
383
|
-
const context = {
|
|
397
|
+
const context = {
|
|
398
|
+
apiVersion: API_VERSION,
|
|
399
|
+
sources: [...sources, ...(input.local ?? [])],
|
|
400
|
+
missing,
|
|
401
|
+
};
|
|
402
|
+
// Пределы снимка контекста проверяются здесь, а не только при запуске:
|
|
403
|
+
// source-snapshot и source-check называют их как отказ eligibility.
|
|
404
|
+
for (const issue of contextLimitIssues(context))
|
|
405
|
+
problems.push({ code: 'LIMIT_EXCEEDED', taskId: '', key: 'context', message: issue });
|
|
384
406
|
const manifest = {
|
|
385
407
|
version: 1,
|
|
386
408
|
provider: config.provider,
|
|
@@ -398,11 +420,45 @@ export function buildSnapshot(input) {
|
|
|
398
420
|
tasks,
|
|
399
421
|
truncated,
|
|
400
422
|
problems,
|
|
423
|
+
...(input.local?.length ? { localContext: input.local.map(localManifest) } : {}),
|
|
401
424
|
};
|
|
402
425
|
return { graph, context, manifest, revision, problems };
|
|
403
426
|
}
|
|
427
|
+
const CONTEXT_SOURCES_MAX = 30;
|
|
428
|
+
/** Нарушения пределов снимка контекста: число источников, размер файла и сумма. */
|
|
429
|
+
export function contextLimitIssues(context) {
|
|
430
|
+
const issues = contextIssues(context);
|
|
431
|
+
if (context.sources.length > CONTEXT_SOURCES_MAX)
|
|
432
|
+
issues.push(`Источников контекста ${context.sources.length} при пределе ${CONTEXT_SOURCES_MAX} (задачи и локальные пакеты)`);
|
|
433
|
+
return issues.map((i) => /больше предела/.test(i)
|
|
434
|
+
? `${i} (fileBytes=${CONTEXT_LIMITS.fileBytes}, totalBytes=${CONTEXT_LIMITS.totalBytes})`
|
|
435
|
+
: i);
|
|
436
|
+
}
|
|
437
|
+
const localManifest = (s) => ({
|
|
438
|
+
id: s.id,
|
|
439
|
+
digest: s.digest,
|
|
440
|
+
files: s.files.length,
|
|
441
|
+
references: s.references.length,
|
|
442
|
+
bytes: [...s.files, ...s.references].reduce((n, f) => n + f.bytes, 0),
|
|
443
|
+
});
|
|
444
|
+
/**
|
|
445
|
+
* Локальные пакеты `tasks[].context` из `contextDir`: те же правила, что у
|
|
446
|
+
* файлового контекста графа (каталог `<contextDir>/<id>`, `references/` —
|
|
447
|
+
* справки). Нет каталога или пакета — NOT_READY до чтения backend.
|
|
448
|
+
*/
|
|
449
|
+
export async function localContext(config, contextDir) {
|
|
450
|
+
const ids = [...new Set(config.tasks.flatMap((t) => t.context ?? []))].sort();
|
|
451
|
+
if (!ids.length)
|
|
452
|
+
return [];
|
|
453
|
+
if (!contextDir)
|
|
454
|
+
throw new HarnessError('INVALID_INPUT', 'source: tasks[].context требует каталог пакетов (contextDir)');
|
|
455
|
+
if (!existsSync(contextDir) || !statSync(contextDir).isDirectory())
|
|
456
|
+
throw new HarnessError('NOT_READY', `Каталог пакетов контекста ${contextDir} не найден (tasks[].context: ${ids.join(', ')})`);
|
|
457
|
+
return (await new FileContextSource(contextDir).snapshot(ids)).sources;
|
|
458
|
+
}
|
|
404
459
|
/** Прочитать, собрать и проверить идентичность: общий путь `source-snapshot` и `run --source`. */
|
|
405
460
|
export async function takeSnapshot(input) {
|
|
461
|
+
const local = await localContext(input.config, input.contextDir);
|
|
406
462
|
const identity = await checkIdentity(input.transport, input.config, input.signal);
|
|
407
463
|
const { reads, rounds } = await readStable(input.transport, input.config.tasks.map((t) => t.ref), input.config.limits.rereads, input.signal);
|
|
408
464
|
return buildSnapshot({
|
|
@@ -411,6 +467,7 @@ export async function takeSnapshot(input) {
|
|
|
411
467
|
reads,
|
|
412
468
|
rounds,
|
|
413
469
|
receiptOf: input.receiptOf,
|
|
470
|
+
...(local.length ? { local } : {}),
|
|
414
471
|
});
|
|
415
472
|
}
|
|
416
473
|
/** Отказ по eligibility/limits: причина и предел в сообщении, без запуска. */
|
|
@@ -19,12 +19,16 @@ export declare class RelayWorkSource implements WorkSource {
|
|
|
19
19
|
config: ConnectorConfig;
|
|
20
20
|
transport: RelayTransport;
|
|
21
21
|
store: ConnectorStore;
|
|
22
|
+
/** Абсолютный каталог пакетов `tasks[].context`. */
|
|
23
|
+
contextDir?: string;
|
|
22
24
|
};
|
|
23
25
|
readonly id: string;
|
|
24
26
|
constructor(options: {
|
|
25
27
|
config: ConnectorConfig;
|
|
26
28
|
transport: RelayTransport;
|
|
27
29
|
store: ConnectorStore;
|
|
30
|
+
/** Абсолютный каталог пакетов `tasks[].context`. */
|
|
31
|
+
contextDir?: string;
|
|
28
32
|
});
|
|
29
33
|
/** Снимок без проверки eligibility (для предпросмотра и диагностики). */
|
|
30
34
|
snapshot(signal?: AbortSignal): Promise<BuiltSnapshot>;
|
|
@@ -26,6 +26,7 @@ export class RelayWorkSource {
|
|
|
26
26
|
config: this.options.config,
|
|
27
27
|
transport: this.options.transport,
|
|
28
28
|
receiptOf: (orderId) => this.options.store.receipts(orderId).at(-1),
|
|
29
|
+
...(this.options.contextDir ? { contextDir: this.options.contextDir } : {}),
|
|
29
30
|
...(signal ? { signal } : {}),
|
|
30
31
|
});
|
|
31
32
|
}
|
package/package.json
CHANGED
package/skills/harness/SKILL.md
CHANGED
|
@@ -39,8 +39,10 @@ shasum -a 256 <file.tgz>
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Use the exact tarball (or exact registry version) you were given and record
|
|
42
|
-
its sha256. Do not install it into the project: the project
|
|
43
|
-
and lockfile stay untouched.
|
|
42
|
+
its sha256. Do not install it into the project's dependencies: the project
|
|
43
|
+
`package.json` and lockfile stay untouched. `<tooldir>` is outside the
|
|
44
|
+
repository or `<repo>/.harness-tools`; the `harness-setup` skill of the
|
|
45
|
+
package covers both, the version pin and upgrades.
|
|
44
46
|
|
|
45
47
|
### 2. Check the machine
|
|
46
48
|
|
|
@@ -65,19 +67,19 @@ Schema of input and response, and the byte limits. Read only.
|
|
|
65
67
|
interruption it resumes from the saved manifest decisions and writes only
|
|
66
68
|
what is missing.
|
|
67
69
|
|
|
68
|
-
| Conflict or refusal | Meaning
|
|
69
|
-
| --------------------------------------------------- |
|
|
70
|
-
| `symlink` | the path or an ancestor (`conflictPath`) is a symlink
|
|
71
|
-
| `ancestor-not-directory` | an ancestor of the path is a file
|
|
72
|
-
| `not-regular-file` | the destination exists and is not a regular file
|
|
73
|
-
| `outside-root` | the destination resolves outside the repository
|
|
74
|
-
| `content-differs` on `.harness/project.json` | an explicit `--writer` or `--profile` differs from the saved manifest
|
|
75
|
-
| `content-differs` on `.harness/policy.json` | the saved policy's writer differs from the manifest or explicit `--writer`
|
|
76
|
-
| `INVALID_INPUT` with a path | not a Git root, invalid manifest or policy (also a truncated write), `--state` inside the repository
|
|
77
|
-
| `NOT_READY` `no-commits` | the repository has no commits
|
|
78
|
-
| `NOT_READY` `stack-ambiguous` / `stack-unsupported` | both lockfiles, or none
|
|
79
|
-
| `NOT_READY` toolchain problems | missing scripts, wrong tool versions
|
|
80
|
-
| `CONTROLLER_BUSY` | the repository is already owned by another controller
|
|
70
|
+
| Conflict or refusal | Meaning | Action |
|
|
71
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `symlink` | the path or an ancestor (`conflictPath`) is a symlink | never write through it; the user replaces the link |
|
|
73
|
+
| `ancestor-not-directory` | an ancestor of the path is a file | ask the user; never delete their files |
|
|
74
|
+
| `not-regular-file` | the destination exists and is not a regular file | ask the user |
|
|
75
|
+
| `outside-root` | the destination resolves outside the repository | ask the user |
|
|
76
|
+
| `content-differs` on `.harness/project.json` | an explicit `--writer` or `--profile` differs from the saved manifest; after an upgrade or a move, also a changed built-in profile, root or API version | keep the saved decision, or the user confirms the change explicitly |
|
|
77
|
+
| `content-differs` on `.harness/policy.json` | the saved policy's writer differs from the manifest or explicit `--writer` | keep the saved decision, or the user confirms the change explicitly |
|
|
78
|
+
| `INVALID_INPUT` with a path | not a Git root, invalid manifest or policy (also a truncated write), `--state` inside the repository | read the issue path; a broken file is removed by a human, not overwritten |
|
|
79
|
+
| `NOT_READY` `no-commits` | the repository has no commits | the user commits first |
|
|
80
|
+
| `NOT_READY` `stack-ambiguous` / `stack-unsupported` | both lockfiles, or none | pass `--profile`; when profile gates do not fit, write `.harness/profile.json` and pass `--profile ./.harness/profile.json` |
|
|
81
|
+
| `NOT_READY` toolchain problems | missing scripts, wrong tool versions | fix the project or hand it to the user; never weaken the policy |
|
|
82
|
+
| `CONTROLLER_BUSY` | the repository is already owned by another controller | stop: a second controller and state are not created |
|
|
81
83
|
|
|
82
84
|
### 4. Plan the graph
|
|
83
85
|
|