@starci/skills 1.1.0 → 1.2.0
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/INDEX.md +3 -1
- package/INDEX.vi.md +3 -1
- package/README.md +6 -1
- package/README.vi.md +6 -1
- package/knowledge/ui/presentation/INDEX.md +5 -0
- package/knowledge/ui/presentation/INDEX.vi.md +1 -0
- package/knowledge/ui/presentation/radius.md +183 -0
- package/knowledge/ui/presentation/radius.vi.md +182 -0
- package/operators/INDEX.md +5 -5
- package/operators/INDEX.vi.md +5 -5
- package/operators/architecture-decide/operator.md +27 -4
- package/operators/architecture-decide/operator.vi.md +23 -4
- package/operators/architecture-decide/self-test.mjs +18 -1
- package/operators/architecture-decide/validate.mjs +29 -1
- package/operators/backend-source-apply/operator.md +197 -182
- package/operators/backend-source-apply/operator.vi.md +190 -178
- package/operators/backend-source-apply/self-test.mjs +257 -257
- package/operators/backend-source-apply/validate.mjs +240 -240
- package/operators/business-decide/self-test.mjs +2 -1
- package/operators/business-decide/validate.mjs +3 -0
- package/operators/content-generate/self-test.mjs +1 -1
- package/operators/frontend-direction-decide/self-test.mjs +1 -1
- package/operators/frontend-presentation-resolve/operator.md +6 -0
- package/operators/frontend-presentation-resolve/operator.vi.md +6 -0
- package/operators/frontend-presentation-resolve/self-test.mjs +6 -5
- package/operators/frontend-presentation-resolve/validate.mjs +30 -6
- package/operators/frontend-source-apply/operator.md +3 -1
- package/operators/frontend-source-apply/operator.vi.md +3 -1
- package/operators/frontend-source-apply/self-test.mjs +1 -1
- package/operators/frontend-surface-audit/self-test.mjs +1 -1
- package/operators/git-publish/self-test.mjs +1 -1
- package/operators/platform-operate/self-test.mjs +1 -1
- package/operators/quality-verify/operator.md +5 -1
- package/operators/quality-verify/operator.vi.md +5 -1
- package/operators/quality-verify/self-test.mjs +15 -2
- package/operators/quality-verify/validate.mjs +21 -1
- package/operators/release-deploy/self-test.mjs +235 -235
- package/operators/uat-verify/operator.md +20 -6
- package/operators/uat-verify/operator.vi.md +21 -7
- package/operators/uat-verify/self-test.mjs +6 -5
- package/operators/uat-verify/validate.mjs +10 -4
- package/operators/workspace-bind/operator.md +18 -4
- package/operators/workspace-bind/operator.vi.md +16 -4
- package/operators/workspace-bind/self-test.mjs +3 -1
- package/operators/workspace-bind/validate.mjs +1 -0
- package/package.json +1 -1
- package/readiness/initialization/workspaces/local-route.schema.json +1 -1
- package/resources/orchestrator.json +14 -8
- package/routing.json +2 -1
- package/scripts/validate-request.mjs +12 -1
- package/scripts/validate-response.mjs +14 -3
- package/scripts/validate-step.mjs +2 -1
- package/scripts/validate-workflows.mjs +8 -1
- package/templates/kinds/architecture-decision.contract.json +1 -0
- package/templates/kinds/architecture-decision.skeleton.md +6 -0
- package/templates/kinds/backend-source-application.contract.json +1 -1
- package/templates/kinds/backend-source-application.skeleton.md +1 -1
- package/templates/kinds/frontend-presentation-resolution.contract.json +1 -1
- package/templates/kinds/frontend-presentation-resolution.skeleton.md +1 -0
- package/templates/kinds/mutations.schema.json +65 -65
- package/templates/kinds/route.schema.json +4 -2
- package/templates/kinds/stack-model.schema.json +23 -1
- package/templates/kinds/workspace-route-binding.contract.json +2 -2
- package/templates/step/response.schema.json +10 -0
- package/templates/step/state.schema.json +205 -0
- package/workflows/README.md +3 -0
- package/workflows/README.vi.md +3 -0
- package/workflows/backend-feature.json +26 -5
- package/workflows/content-unit.json +4 -1
- package/workflows/frontend-new-surface.json +15 -5
- package/workflows/frontend-reconstruct.json +10 -4
- package/workflows/frontend-refine.json +10 -4
- package/workflows/frontend-with-uat.json +15 -3
- package/workflows/full-feature.json +31 -8
- package/workflows/release.json +7 -1
|
@@ -29,6 +29,18 @@ tiếp, quyền sở hữu datastore, sao lưu và khôi phục; phán quyết b
|
|
|
29
29
|
không sở hữu; một store gọi tên đúng một boundary chủ và boundary đó ghi nó, người ghi thứ hai chỉ
|
|
30
30
|
tồn tại khi có lý do chia sẻ ghi rõ ràng.
|
|
31
31
|
|
|
32
|
+
## Quyết định gọi tên mọi lần ghi nó cam kết
|
|
33
|
+
|
|
34
|
+
Một boundary sở hữu store không nói gì về việc ai ghi nó, lúc nào, dưới transaction nào, nên quyết
|
|
35
|
+
định tự lấp khoảng trống đó: `response/data/stack-model.json` mang một mục `operations` cho mỗi lần
|
|
36
|
+
ghi mà kiến trúc này cam kết, và bảng `## Operations` của biên nhận thuật lại đúng những hàng ấy. Mỗi
|
|
37
|
+
mục gọi tên transport, writer, các store nó chạm, ranh giới transaction, kiểu idempotency, các
|
|
38
|
+
migration nó mang, và các `dimension` của ma trận phủ thuộc head nghiệp vụ mà nó hiện thực. Danh sách
|
|
39
|
+
đó chính là hợp đồng đóng băng mà `backend.source.apply` lấp: phần hiện thực thuật lại nguyên vẹn
|
|
40
|
+
những operation đó và không được thêm cái nào, nên một operation không ai khai ở đây thì không thể
|
|
41
|
+
được viết ở đâu cả. Khai một lần ghi không phải là chọn một cách hiện thực, và đó là lý do writer là
|
|
42
|
+
đường dẫn file duy nhất operator này gọi tên.
|
|
43
|
+
|
|
32
44
|
## Phản biện là một cuộc trao đổi lồng
|
|
33
45
|
|
|
34
46
|
Sau khi phương án đã chọn được đào sâu, nhánh tạm ngưng: nó phát `response/response.json` với status
|
|
@@ -47,12 +59,16 @@ source được route, không publish thẩm quyền nghiệp vụ, không khở
|
|
|
47
59
|
runtime, không nêu tên file implementation trong handoff, và không tuyên bố implementation, cổng
|
|
48
60
|
chất lượng hay UAT đã qua.
|
|
49
61
|
|
|
62
|
+
Khi đầu vào `model` có mặt, nó là thẩm quyền của lần chạy này và head đã publish chỉ còn là dòng dõi,
|
|
63
|
+
bởi một quyết định lấy theo lời hứa của hôm qua là quyết định lấy theo lời hứa sai. Khi nó vắng, head
|
|
64
|
+
đã publish là thẩm quyền.
|
|
65
|
+
|
|
50
66
|
## Context
|
|
51
67
|
|
|
52
68
|
| Alias | Bind | Bắt buộc |
|
|
53
69
|
| --- | --- | --- |
|
|
54
70
|
| `@workspaces/be` | checkout backend được route, đọc ở head đóng băng; inventory lấy từ manifest và file deploy | có |
|
|
55
|
-
| `@worktrees/businesses/<featureId>` | head nghiệp vụ đã publish, lời hứa mà kiến trúc phải
|
|
71
|
+
| `@worktrees/businesses/<featureId>` | head nghiệp vụ đã publish, lời hứa mà kiến trúc phải giữ; chỉ là bằng chứng khi phiên mang đầu vào `model` | có |
|
|
56
72
|
| `@knowledge/patterns` | hình dạng tái dùng mà scope có thể ràng; là hình dạng, không bao giờ là lựa chọn | không |
|
|
57
73
|
|
|
58
74
|
## Đầu vào
|
|
@@ -60,6 +76,7 @@ chất lượng hay UAT đã qua.
|
|
|
60
76
|
| Kind | Từ đâu | Bắt buộc |
|
|
61
77
|
| --- | --- | --- |
|
|
62
78
|
| `architecture-decision` | một lần chạy `architecture.decide` trước trên cùng hoặc kề ranh giới; dòng dõi có thể bị phản bác, không được bỏ qua | không |
|
|
79
|
+
| `model` | `business.decide`; head mà nhánh đó đã mô hình hoá, khi nó chưa được publish | không |
|
|
63
80
|
|
|
64
81
|
## Yêu cầu
|
|
65
82
|
|
|
@@ -80,11 +97,11 @@ chất lượng hay UAT đã qua.
|
|
|
80
97
|
| --- | --- | --- | --- | --- | --- |
|
|
81
98
|
| 1 | Kiểm gate và chạy lại | `resume`, `approval` | `request/request.json`, đầu vào `architecture-decision` nếu có, @workspaces/be ở head đóng băng | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
82
99
|
| 2 | Quan sát hiện trạng | — | @workspaces/be ở head đóng băng: manifest, cấu hình, file deploy, @tools/git | `response/data/current-state.json` | `CURRENT_STATE_UNOBSERVED` |
|
|
83
|
-
| 3 | Ràng inventory với lời hứa nghiệp vụ | — | `response/data/current-state.json`, @worktrees/businesses/<featureId> ở head đã publish | — | `BUSINESS_AUTHORITY_REQUIRED`, `EVIDENCE_MISSING` |
|
|
100
|
+
| 3 | Ràng inventory với lời hứa nghiệp vụ | — | `response/data/current-state.json`, đầu vào `model` khi có, nếu không thì @worktrees/businesses/<featureId> ở head đã publish | — | `BUSINESS_AUTHORITY_REQUIRED`, `EVIDENCE_MISSING` |
|
|
84
101
|
| 4 | Đóng khung quyết định | `objective`, `decisionId`, `constraints`, `tradeoffAxes` | phần requirements của `request/request.json` | — | `CONSTRAINT_CONTRADICTION` |
|
|
85
102
|
| 5 | Sinh các phương án | `alternatives` | `response/data/current-state.json`, @knowledge/patterns, @tools/websearch | `response/artifacts/<decisionId>-alternatives.html` chỉ khi được yêu cầu nhiều hơn một phương án, @tools/visualize | `NO_VIABLE_ALTERNATIVE` |
|
|
86
103
|
| 6 | Chọn | `selectionPolicy`, `tradeoffAxes`, `approval` | `response/artifacts/<decisionId>-alternatives.html` khi có | — | `CHOICE_REQUIRED` |
|
|
87
|
-
| 7 | Đào sâu phương án đã chọn | `constraints` | `response/data/current-state.json
|
|
104
|
+
| 7 | Đào sâu phương án đã chọn và khai các operation nó cam kết | `constraints` | `response/data/current-state.json`, ma trận phủ của head nghiệp vụ đã ràng cho các dimension mỗi operation trích dẫn | `response/data/stack-model.json`, gồm cả `operations` của nó | `DATA_OWNERSHIP_UNASSIGNED`, `COMPATIBILITY_UNVERIFIED` |
|
|
88
105
|
| 8 | Chờ phản biện: tạm ngưng, một agent mới tấn công lựa chọn, chạy tiếp khi nó trả lời | — | `critique/response/critique.md` khi cuộc trao đổi done | `response/response.json` (waiting, awaiting critique) | `CRITIQUE_UNRESOLVED` |
|
|
89
106
|
| 9 | Xác nhận hoặc trả lại lựa chọn | `selectionPolicy` | `critique/response/critique.md`, `response/data/stack-model.json` | — | `CHOICE_REQUIRED`, `NO_VIABLE_ALTERNATIVE` |
|
|
90
107
|
| 10 | Viết handoff và phát | — | mọi thứ ở trên | `response/response.md`, `response/response.json` | — |
|
|
@@ -92,7 +109,9 @@ chất lượng hay UAT đã qua.
|
|
|
92
109
|
Với mặc định, bước 5 sinh một thiết kế và không có trang so sánh, bước 6 không có gì để chọn, và
|
|
93
110
|
chất lượng quyết định dựa vào bước 8. Khi phương án duy nhất chết dưới một đòn tấn công, bước 9 dừng
|
|
94
111
|
với `NO_VIABLE_ALTERNATIVE`, không phải `CHOICE_REQUIRED`. Handoff nêu tên contract, không bao giờ
|
|
95
|
-
nêu file implementation, vì chọn file là việc của domain kế tiếp
|
|
112
|
+
nêu file implementation, vì chọn file là việc của domain kế tiếp; ngoại lệ duy nhất là writer của mỗi
|
|
113
|
+
operation đã khai, cái mà operator này có gọi tên, bởi phần hiện thực không được tự chọn writer cho
|
|
114
|
+
mình.
|
|
96
115
|
|
|
97
116
|
## Đầu ra
|
|
98
117
|
|
|
@@ -34,6 +34,14 @@ function stackModel({ alternatives = 1 } = {}) {
|
|
|
34
34
|
{ boundaryId: 'course-api', responsibility: 'serves course content', owner: 'learning-team', interfaces: ['CourseQuery'], ownsData: false },
|
|
35
35
|
],
|
|
36
36
|
stores: [{ storeId: 'entitlement-store', owningBoundaryId: 'entitlement', writers: ['entitlement'], readers: ['course-api'], migrators: ['entitlement'], transactionScope: 'per request', backup: 'nightly snapshot', restore: 'tested weekly', sharedWriteJustification: null }],
|
|
37
|
+
operations: [
|
|
38
|
+
{
|
|
39
|
+
operationId: 'grant-entitlement', name: 'grantEntitlement', transport: 'graphql-mutation',
|
|
40
|
+
writerRef: 'src/entitlement/graphql/mutations/grant-entitlement.ts', storeRefs: ['entitlement-store'],
|
|
41
|
+
transactionBoundary: 'single-transaction', idempotencyKind: 'request-token', migrationRefs: [],
|
|
42
|
+
authorityDimensionIds: ['effective-access'],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
37
45
|
components: [
|
|
38
46
|
{ componentId: 'nestjs', status: 'existing', justification: 'observed-evidence', evidence: ev('package.json:12'), compatibility: verdicts() },
|
|
39
47
|
{ componentId: 'postgres', status: 'existing', justification: 'measured-constraint', evidence: ev('compose.yaml:30'), compatibility: verdicts() },
|
|
@@ -89,6 +97,12 @@ ${altRows.join('\n')}
|
|
|
89
97
|
| \`postgres\` | existing | measured-constraint | \`compose.yaml:30@${head}\` | 5/5 verified |
|
|
90
98
|
| \`redis-cache\` | removed | — | — | — |
|
|
91
99
|
|
|
100
|
+
## Operations
|
|
101
|
+
|
|
102
|
+
| Operation | Transport | Writer | Stores | Transaction | Idempotency | Dimensions |
|
|
103
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
104
|
+
| \`grant-entitlement\` | graphql-mutation | \`src/entitlement/graphql/mutations/grant-entitlement.ts\` | \`entitlement-store\` | single-transaction | request-token | effective-access |
|
|
105
|
+
|
|
92
106
|
## Handoff
|
|
93
107
|
|
|
94
108
|
| Item | Kind | Detail |
|
|
@@ -148,7 +162,7 @@ function writeBranch(files) {
|
|
|
148
162
|
const session = mkdtempSync(path.join(tmpdir(), 'arch-session-'));
|
|
149
163
|
const branch = path.join(session, 'step-1', 'parallel-1');
|
|
150
164
|
for (const d of ['request', 'response/data', 'response/artifacts', 'critique/request', 'critique/response']) mkdirSync(path.join(branch, d), { recursive: true });
|
|
151
|
-
writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: 's-test', chain: [['1/1']], steps: { '1/1': 'architecture.decide' }, current: '1/1', status: 'running' }));
|
|
165
|
+
writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: 's-test', project: 'starci-academy', startedAt: '2026-09-03T00:00:00Z', requestHashes: {}, chain: [['1/1']], steps: { '1/1': 'architecture.decide' }, current: '1/1', status: 'running' }));
|
|
152
166
|
for (const [name, content] of Object.entries(files)) {
|
|
153
167
|
if (content === null) continue;
|
|
154
168
|
writeFileSync(path.join(branch, name), typeof content === 'string' ? content : JSON.stringify(content, null, 2));
|
|
@@ -213,6 +227,9 @@ await expectError({ ...baseline(), 'response/data/stack-model.json': { ...stackM
|
|
|
213
227
|
await expectError({ ...baseline(), 'response/data/stack-model.json': stackModel({ alternatives: 2 }) }, 'but the request asked for 1', 'more alternatives than asked');
|
|
214
228
|
await expectError({ ...baseline(), 'response/response.md': responseMd({ handoffDetail: 'src/entitlement/query.ts returns one answer' }) }, 'names an implementation file', 'handoff names a file');
|
|
215
229
|
await expectError({ ...baseline(), 'response/response.md': responseMd().replace('| Selected alternative | `shared-boundary` |', '| Selected alternative | `edge-cache` |') }, 'Decision names edge-cache', 'response and model disagree on the selection');
|
|
230
|
+
await expectError({ ...baseline(), 'response/data/stack-model.json': (() => { const m = stackModel(); m.operations = [{ ...m.operations[0], storeRefs: ['ghost-store'] }]; return m; })() }, 'which this decision does not own', 'an operation writing a store the decision does not own');
|
|
231
|
+
await expectError({ ...baseline(), 'response/data/stack-model.json': (() => { const m = stackModel(); m.operations = [{ ...m.operations[0], transport: 'event-consumer', idempotencyKind: 'none' }]; return m; })() }, 'a redelivery applies it twice', 'an event consumer with no idempotency');
|
|
232
|
+
await expectError({ ...baseline(), 'response/data/stack-model.json': (() => { const m = stackModel(); m.operations = [{ ...m.operations[0], authorityDimensionIds: ['another-dimension'] }]; return m; })() }, 'does not restate declared dimension another-dimension', 'a receipt that drops a declared dimension');
|
|
216
233
|
await expectError({ ...baseline(), 'response/response.md': responseMd().replace('## Handoff', '## Hand-off') }, 'missing section ^## Handoff$', 'response section renamed');
|
|
217
234
|
await expectError({ ...baseline(), 'response/data/current-state.json': { ...currentState(), observedHead: 'nope' } }, 'observedHead', 'current-state schema');
|
|
218
235
|
await expectError({ ...baseline(), 'response/response.json': (() => { const o = responseJson(); delete o.fields['stack-model']; return o; })() }, 'required output stack-model is not in fields', 'missing required output');
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// owner among its writers and shared writes are justified; retained components are verified on all
|
|
5
5
|
// five axes or carry the COMPATIBILITY_UNVERIFIED fallback; the critique came from the nested
|
|
6
6
|
// exchange, attacks the selected alternative, and a failing attack cannot end in status done; the
|
|
7
|
-
// handoff names contracts, never implementation files
|
|
7
|
+
// handoff names contracts, never implementation files; every declared operation is named once, names a
|
|
8
|
+
// writer, cites at least one business dimension, and is restated by the receipt's Operations table.
|
|
8
9
|
import { existsSync } from 'node:fs';
|
|
9
10
|
import { readFile } from 'node:fs/promises';
|
|
10
11
|
import path from 'node:path';
|
|
@@ -62,6 +63,14 @@ export async function validateArchitectureStep(branchDir, root = ROOT) {
|
|
|
62
63
|
if (b.ownsData && n === 0) errors.push(`response/data/stack-model.json: boundary ${b.boundaryId} claims data and owns no store`);
|
|
63
64
|
if (!b.ownsData && n > 0) errors.push(`response/data/stack-model.json: boundary ${b.boundaryId} claims no data and owns ${n} store(s)`);
|
|
64
65
|
}
|
|
66
|
+
const seenOperations = new Set();
|
|
67
|
+
for (const o of model.operations ?? []) {
|
|
68
|
+
if (seenOperations.has(o.operationId)) errors.push(`response/data/stack-model.json: operation ${o.operationId} is declared twice`);
|
|
69
|
+
seenOperations.add(o.operationId);
|
|
70
|
+
for (const storeRef of o.storeRefs) if (!model.stores.some((st) => st.storeId === storeRef)) errors.push(`response/data/stack-model.json: operation ${o.operationId} names store ${storeRef}, which this decision does not own`);
|
|
71
|
+
if (o.transactionBoundary === 'read-only' && o.migrationRefs.length) errors.push(`response/data/stack-model.json: operation ${o.operationId} is read-only and ships a migration`);
|
|
72
|
+
if (o.transport === 'event-consumer' && o.idempotencyKind === 'none') errors.push(`response/data/stack-model.json: operation ${o.operationId} consumes events with no idempotency, so a redelivery applies it twice`);
|
|
73
|
+
}
|
|
65
74
|
const fallbacks = new Set(response.fallbacks ?? []);
|
|
66
75
|
for (const c of model.components) {
|
|
67
76
|
if (c.status === 'removed') { if (c.compatibility.length) errors.push(`response/data/stack-model.json: removed component ${c.componentId} carries a compatibility verdict`); continue; }
|
|
@@ -81,6 +90,25 @@ export async function validateArchitectureStep(branchDir, root = ROOT) {
|
|
|
81
90
|
if (!empty(requirements.decisionId) && decision['Decision id'] !== requirements.decisionId) errors.push('response/response.md: Decision id differs from the request');
|
|
82
91
|
const alts = tableUnder(text, '## Alternatives') ?? [];
|
|
83
92
|
if (model && alts.length !== model.alternatives.length) errors.push(`response/response.md: Alternatives has ${alts.length} rows, stack-model has ${model.alternatives.length}`);
|
|
93
|
+
const operationRows = tableUnder(text, '## Operations') ?? [];
|
|
94
|
+
if (model) {
|
|
95
|
+
const declared = new Map((model.operations ?? []).map((o) => [o.operationId, o]));
|
|
96
|
+
if (operationRows.length !== declared.size) errors.push(`response/response.md: Operations has ${operationRows.length} rows, stack-model declares ${declared.size}`);
|
|
97
|
+
for (const [operationId, transport, writer, stores, transaction, idempotency, dimensions] of operationRows) {
|
|
98
|
+
const id = String(operationId).replace(/`/g, '');
|
|
99
|
+
const o = declared.get(id);
|
|
100
|
+
if (!o) { errors.push(`response/response.md: Operations names ${id}, which stack-model does not declare`); continue; }
|
|
101
|
+
if (transport !== o.transport) errors.push(`response/response.md: operation ${id} reports transport ${transport}, stack-model declares ${o.transport}`);
|
|
102
|
+
if (String(writer).replace(/`/g, '') !== o.writerRef) errors.push(`response/response.md: operation ${id} names another writer than stack-model`);
|
|
103
|
+
if (transaction !== o.transactionBoundary) errors.push(`response/response.md: operation ${id} reports transaction ${transaction}, stack-model declares ${o.transactionBoundary}`);
|
|
104
|
+
if (idempotency !== o.idempotencyKind) errors.push(`response/response.md: operation ${id} reports idempotency ${idempotency}, stack-model declares ${o.idempotencyKind}`);
|
|
105
|
+
const cited = String(dimensions).split(',').map((v) => v.trim().replace(/`/g, '')).filter(Boolean);
|
|
106
|
+
for (const dimensionId of cited) if (!o.authorityDimensionIds.includes(dimensionId)) errors.push(`response/response.md: operation ${id} cites dimension ${dimensionId}, which stack-model does not declare`);
|
|
107
|
+
for (const dimensionId of o.authorityDimensionIds) if (!cited.includes(dimensionId)) errors.push(`response/response.md: operation ${id} does not restate declared dimension ${dimensionId}`);
|
|
108
|
+
const declaredStores = String(stores).split(',').map((v) => v.trim().replace(/`/g, '')).filter((v) => v && v !== '—');
|
|
109
|
+
for (const storeRef of o.storeRefs) if (!declaredStores.includes(storeRef)) errors.push(`response/response.md: operation ${id} does not restate store ${storeRef}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
84
112
|
for (const [item, kind, detail] of tableUnder(text, '## Handoff') ?? []) if (kind === 'contract' && IMPLEMENTATION_FILE.test(detail)) errors.push(`response/response.md: handoff contract "${item}" names an implementation file`);
|
|
85
113
|
for (const [component, status] of tableUnder(text, '## Stack delta') ?? []) if (status === 'replaced-candidate' && !(response.fallbacks ?? []).includes('COMPATIBILITY_UNVERIFIED')) errors.push(`response/response.md: ${component} is replaced-candidate without the COMPATIBILITY_UNVERIFIED fallback`);
|
|
86
114
|
}
|
|
@@ -1,189 +1,204 @@
|
|
|
1
|
-
# backend.source.apply
|
|
2
|
-
|
|
3
|
-
## Job
|
|
4
|
-
|
|
5
|
-
Implement one backend outcome inside a frozen mutation contract, following the observed sibling
|
|
6
|
-
family, and return the measured conformance and proof receipt that shows the boundary was not widened.
|
|
7
|
-
|
|
8
|
-
## The contract is frozen before the first write
|
|
9
|
-
|
|
10
|
-
The contract arrives as the Input `architecture-decision`, fingerprinted and closed. The operations,
|
|
11
|
-
writers, stores, transaction boundaries, idempotency kinds, and migrations it lists are the complete
|
|
12
|
-
set the implementation may touch, and this operator answers only one question per operation: does the
|
|
13
|
-
code that now exists do exactly what the contract says, and what measurement shows it. The operations
|
|
14
|
-
are not a Requirement, because a person retyping a contract into a request is how the contract and
|
|
15
|
-
the implementation quietly diverge; step 3 reads them from the frozen input
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
the
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
approved
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
##
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
the
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
| `
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
|
132
|
-
|
|
1
|
+
# backend.source.apply
|
|
2
|
+
|
|
3
|
+
## Job
|
|
4
|
+
|
|
5
|
+
Implement one backend outcome inside a frozen mutation contract, following the observed sibling
|
|
6
|
+
family, and return the measured conformance and proof receipt that shows the boundary was not widened.
|
|
7
|
+
|
|
8
|
+
## The contract is frozen before the first write
|
|
9
|
+
|
|
10
|
+
The contract arrives as the Input `architecture-decision`, fingerprinted and closed. The operations,
|
|
11
|
+
writers, stores, transaction boundaries, idempotency kinds, and migrations it lists are the complete
|
|
12
|
+
set the implementation may touch, and this operator answers only one question per operation: does the
|
|
13
|
+
code that now exists do exactly what the contract says, and what measurement shows it. The operations
|
|
14
|
+
are not a Requirement, because a person retyping a contract into a request is how the contract and
|
|
15
|
+
the implementation quietly diverge; step 3 reads them from the frozen input's `operations` — one row of the
|
|
16
|
+
architecture decision's `## Operations` table, one object of its `stack-model.json`, per write the
|
|
17
|
+
decision commits to — and restates them in `response/data/mutations.json`, carrying each operation's
|
|
18
|
+
`writerRef`, `transactionBoundary`, `idempotencyKind`, `migrationRefs` and dimension ids across
|
|
19
|
+
unchanged. Three prohibitions carry that, and each is enforced rather than
|
|
20
|
+
advised. An operation, writer, store, transaction, migration, or event outside the contract is
|
|
21
|
+
`CONTRACT_WIDENED`, returned to the contract owner before any product write. A file outside the
|
|
22
|
+
mutable ceiling is `OWNER_CONFLICT`, even when the change there would be one line. A convention no
|
|
23
|
+
bound sibling pattern publishes is refused and recorded as `NEW_CONVENTION_REFUSED`, while an aspect
|
|
24
|
+
with no pattern at all is `PATTERN_UNBOUND`. Discovering mid-implementation that the outcome needs a
|
|
25
|
+
wider boundary is the expected way this operator ends, not a failure of nerve: the contract is
|
|
26
|
+
reopened by its owner and the same outcome is implemented again against the new fingerprint. Reaching
|
|
27
|
+
outside the list is not a smaller change than reopening the contract; it is the same change made
|
|
28
|
+
without a record.
|
|
29
|
+
|
|
30
|
+
## The person's branch is never written
|
|
31
|
+
|
|
32
|
+
This operator never writes on the branch a person has checked out. The orchestrator prepares a
|
|
33
|
+
dedicated git worktree of the routed checkout on the session branch `session/<sessionId>`, cut from
|
|
34
|
+
the frozen head, and step 3 writes there and nowhere else, under an exclusive lease on
|
|
35
|
+
`@workspaces/be`. The final step commits the whole declared write set once, records that sha in
|
|
36
|
+
`response.json.commits`, and names the same sha in `response/data/mutations.json` as `commit` beside
|
|
37
|
+
the `base` it started from and the `branch` it lives on; `response/changes.md` states the same move in
|
|
38
|
+
its Binding row, `@workspaces/be` at `<base>` → `<sha>` on `session/<sessionId>`. One commit, because
|
|
39
|
+
a step whose work arrives as several commits cannot be pinned by the next step's request, and an
|
|
40
|
+
uncommitted write cannot be pinned at all. Nothing is pushed and nothing is merged here: `git.publish`
|
|
41
|
+
merges the session branch into the target branch, and it is the only operator that talks to a remote.
|
|
42
|
+
|
|
43
|
+
## Dry mode writes the plan, not the tree
|
|
44
|
+
|
|
45
|
+
`mode` decides whether this run touches the checkout at all. Under `apply` the operator fills the
|
|
46
|
+
contract, commits once, and everything below holds as written. Under `dry` it does the same reading,
|
|
47
|
+
the same binding and the same projection, then stops after the plan: `response/data/mutations.json`
|
|
48
|
+
carries the operations it would fill and the files it would touch with `commit` null and no after
|
|
49
|
+
hash, `response.json` records no commit, and not one byte reaches `@workspaces/be`. The branch still
|
|
50
|
+
ends `done`, because a plan honestly produced is a finished answer to a question about a plan; its
|
|
51
|
+
`changes.md` lists every planned path as `unchanged`, which is what the working tree actually shows,
|
|
52
|
+
and names the change it would have made in `Why`. A dry run measures nothing, so it carries no
|
|
53
|
+
conformance record and no proof record: a facet cannot be measured on code that was never written,
|
|
54
|
+
and a plan that shipped verdicts would be indistinguishable from an implementation. A dry run is also granted neither `@tools/sourcewrite` nor `@tools/git`, because a
|
|
55
|
+
mode that writes nothing needs no tool that can write; the grant and this paragraph say the same
|
|
56
|
+
thing so neither can drift. That is also why
|
|
57
|
+
a dry run can never be the run that satisfies the contract — it is a way to read the write set before
|
|
58
|
+
paying for it, not a cheaper way to apply it.
|
|
59
|
+
|
|
60
|
+
## The backend never invents business behaviour
|
|
61
|
+
|
|
62
|
+
Every operation cites the approved decisions it implements. An approved decision is not a number this
|
|
63
|
+
operator may coin: it is a coverage-matrix `dimension` of the bound business head, addressed by that
|
|
64
|
+
dimension's own kebab identifier, and the matrix fingerprint travels with the citation so a later
|
|
65
|
+
reader can tell which matrix approved it. A citation naming anything the bound matrix does not carry
|
|
66
|
+
is not an approval, it is a guess with a label. When the code reaches a point where the
|
|
67
|
+
answer depends on a business rule nobody approved, the branch stops with `BUSINESS_AUTHORITY_MISSING`
|
|
68
|
+
and names the open question. It does not pick the lenient reading, mirror what a neighbouring feature
|
|
69
|
+
happens to do, or choose whichever branch makes the test go green. This is the most load-bearing rule
|
|
70
|
+
in the operator, because a guessed business rule that passes its own test is indistinguishable from an
|
|
71
|
+
approved one once it ships. An implemented receipt therefore cannot carry a `BUSINESS_QUESTION_RAISED`
|
|
72
|
+
finding: raising the question and implementing anyway is the exact contradiction the check exists to
|
|
73
|
+
catch.
|
|
74
|
+
|
|
75
|
+
## Sibling patterns are the only source of convention
|
|
76
|
+
|
|
77
|
+
The bound patterns name one family per aspect, and the implementation mirrors the family the codebase
|
|
78
|
+
already publishes rather than the one it remembers: command handlers in the family the mutation layer
|
|
79
|
+
already uses, exceptions derived from the published exception identity, entity access through the
|
|
80
|
+
injected primary entity manager, migrations under the primary datasource. Two families bound for one
|
|
81
|
+
aspect means no family is bound, and guessing the family from memory is how a second house style
|
|
82
|
+
enters a codebase unnoticed.
|
|
83
|
+
|
|
84
|
+
## Conformance is measured, not asserted
|
|
85
|
+
|
|
86
|
+
A conformance record without evidence is a sentence about the code, and a sentence cannot contradict
|
|
87
|
+
the code. Each declared facet of each operation gets its own file,
|
|
88
|
+
`response/data/conformance/<operationId>.<facet>.json`, so a facet nobody measured is a missing file
|
|
89
|
+
rather than a missing line inside a file that still looks complete. The evidence is what a later
|
|
90
|
+
reader uses to disagree with this receipt, so it is required for every facet including the ones that
|
|
91
|
+
passed. The same reasoning makes a proof carry its command, its exit code and its output in
|
|
92
|
+
`response/data/proofs/<operationId>.<kind>.json`: the command says what was run and the result says
|
|
93
|
+
what came back, and either one alone can be written by someone who ran nothing. A proof that could not
|
|
94
|
+
run never becomes an assertion that the behaviour is fine, and a failed proof blocks the receipt
|
|
95
|
+
rather than being reclassified. Each touched file carries one change record with its kind and its
|
|
96
|
+
before and after hashes, because a modified file whose two hashes agree records a mutation that did
|
|
97
|
+
not happen.
|
|
98
|
+
|
|
99
|
+
## Boundary
|
|
100
|
+
|
|
101
|
+
The operator writes product source only inside the mutable file ceiling, only inside the session
|
|
102
|
+
branch worktree of `@workspaces/be`, and writes everything else into `response/` of its own branch:
|
|
103
|
+
`response.md`, `response/changes.md`, `response/data/mutations.json`, one conformance record per
|
|
104
|
+
declared facet, one proof record per declared proof, and `response.json`. It never adds an operation,
|
|
105
|
+
writer, store, transaction, migration, or event the frozen contract does not carry, decides a business
|
|
106
|
+
rule the approved authority does not state, introduces a convention no bound sibling pattern
|
|
107
|
+
publishes, weakens, skips, suppresses, or substitutes a declared proof to make a run go green, edits
|
|
108
|
+
the contract, the business authority, or a file outside the mutable ceiling, commits more than once,
|
|
109
|
+
writes on the person's checked-out branch, pushes, merges, or tags anything, claims conformance
|
|
110
|
+
without naming the evidence that measured it, or records a quality, visual, or UAT verdict; those are
|
|
111
|
+
other jobs with their own gates.
|
|
112
|
+
|
|
113
|
+
When the `model` Input is present it is the authority for this run and the published head is lineage
|
|
114
|
+
only: a chain that has just modelled a head must not decide against an older promise merely because
|
|
115
|
+
the publication was withheld. When it is absent the published head is the authority.
|
|
116
|
+
|
|
117
|
+
## Context
|
|
118
|
+
|
|
119
|
+
| Alias | Bind | Required |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `@worktrees/businesses/<featureId>` | the published business head, the only source of business behaviour; evidence when the session carries a `model` Input | yes |
|
|
122
|
+
| `@knowledge/patterns/be` | the sibling families this change mirrors, one per aspect; the only source of valid conventions | yes |
|
|
123
|
+
| `@workspaces/be` | the routed backend checkout at the frozen head, written only on its session branch worktree | yes |
|
|
124
|
+
|
|
125
|
+
## Inputs
|
|
126
|
+
|
|
127
|
+
| Kind | From | Required |
|
|
128
|
+
| --- | --- | --- |
|
|
129
|
+
| `architecture-decision` | `architecture.decide`; the frozen mutation contract the implementation fills and may not widen, and the source of every operation this run restates | yes |
|
|
130
|
+
| `model` | `business.decide`; the head that branch modelled, when it has not been published yet | no |
|
|
131
|
+
| `backend-source-application` | a prior run of `backend.source.apply` for the same outcome; regression history, absent on the first run | no |
|
|
132
|
+
|
|
133
|
+
## Requirements
|
|
134
|
+
|
|
135
|
+
| Field | Type | Default | Ask |
|
|
136
|
+
| --- | --- | --- | --- |
|
|
137
|
+
| `featureId` | id | — | The feature whose published business head decides this behaviour |
|
|
138
|
+
| `outcome` | prompt | — | The one thing being implemented, in the person's words |
|
|
139
|
+
| `mutableFileRefs` | list | — | The only files product source may be written into |
|
|
140
|
+
| `mode` | choice | apply | `apply` fills the contract and commits, `dry` emits the plan and writes nothing |
|
|
141
|
+
| `resume` | token | null | The blocked branch's token when re-entering after a stop |
|
|
142
|
+
|
|
143
|
+
## Steps
|
|
144
|
+
|
|
145
|
+
| # | Step | Params | Reads | Writes | Stops with |
|
|
146
|
+
| --- | --- | --- | --- | --- | --- |
|
|
133
147
|
| 1 | Validate the gate and resume | `resume`, `mode` | `request/request.json`, input `backend-source-application` when present, @workspaces/be at the frozen head | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
|
|
134
|
-
| 2 | Bind authority, contract and patterns | `featureId` | @worktrees/businesses/<featureId> at its published head, input `architecture-decision` as the frozen contract
|
|
148
|
+
| 2 | Bind authority, contract and patterns | `featureId` | input `model` when present, otherwise @worktrees/businesses/<featureId> at its published head, input `architecture-decision` as the frozen contract and the source of its `operations`, @knowledge/patterns/be one pattern per aspect | — | `CONTRACT_UNFROZEN`, `BUSINESS_AUTHORITY_MISSING`, `PATTERN_UNBOUND` |
|
|
135
149
|
| 3 | Fill one contract operation at a time, on the session branch | `mutableFileRefs` | @knowledge/patterns/be for each aspect, @workspaces/be inside the mutable ceiling | @workspaces/be/branch/session inside the mutable ceiling, under an exclusive lease, @tools/sourcewrite | `CONTRACT_WIDENED`, `OWNER_CONFLICT` |
|
|
136
150
|
| 4 | Check every mutation against the frozen contract and record it with its before and after hash | `mode` | @workspaces/be, the touched files and the frozen contract | `response/data/mutations.json` | — |
|
|
137
151
|
| 5 | Revalidate persisted snapshots on read | — | @workspaces/be, the persisted snapshot, @knowledge/patterns/be for the rules that drift after it | — | — |
|
|
138
152
|
| 6 | Prove each declared facet | — | @workspaces/be, the measurement behind each facet | `response/data/conformance/<operationId>.<facet>.json` | — |
|
|
139
153
|
| 7 | Run each declared proof | — | @workspaces/be, the pinned command of each declared proof kind | `response/data/proofs/<operationId>.<proofKind>.json`, @tools/shell | `PROOF_UNAVAILABLE` |
|
|
140
154
|
| 8 | Commit the write set once, write the receipt and emit | `outcome` | everything above | @workspaces/be/branch/session as one commit, `response/changes.md`, `response/response.md`, `response/response.json`, @tools/git | — |
|
|
141
|
-
|
|
142
|
-
Under `mode = dry` step 3 projects the fill onto the declared paths without writing one of them, step
|
|
143
|
-
4 records that projection as the plan with a null commit and no after hash, steps 5 to 7 have nothing
|
|
144
|
-
to measure and produce nothing, and step 8 emits the receipt and the change record without a commit.
|
|
145
|
-
Under `apply` every step runs as written. The routed head is reverified immediately before the first
|
|
146
|
-
product write, so drift found there stops the branch before anything is written. Filling an operation writes the transport, the validation, the
|
|
147
|
-
authorization check, the data access, and the failure paths into the declared writer and the files the
|
|
148
|
-
change genuinely requires; it refuses loudly and early rather than dropping a case silently, raising
|
|
149
|
-
the exception the exception-identity pattern publishes before any row or external checkout is created.
|
|
150
|
-
When the outcome persists a workflow, session, cart, draft, or other snapshot, usability is enforced
|
|
151
|
-
again where it is read, reconciled server side, in stable order, with indexes remapped atomically and
|
|
152
|
-
an explicit terminal state when nothing actionable remains, and recorded as `SNAPSHOT_REVALIDATED`. A
|
|
153
|
-
resume begins again at step 1, reuses only unchanged fingerprinted observations, and consumes the exact
|
|
154
|
-
delta; an approved business decision arrives as a new authority fingerprint, because the same
|
|
155
|
-
fingerprint cannot yield a different answer.
|
|
156
|
-
|
|
157
|
-
## Outputs
|
|
158
|
-
|
|
159
|
-
| Kind | File | Type | Required |
|
|
160
|
-
| --- | --- | --- | --- |
|
|
161
|
-
| `backend-source-application` | `response/response.md` | md | yes |
|
|
162
|
-
| `changes` | `response/changes.md` | md | yes |
|
|
163
|
-
| `mutations` | `response/data/mutations.json` | data | yes |
|
|
164
|
-
| `conformance` | `response/data/conformance/<operationId>.<facet>.json` | data | no |
|
|
165
|
-
| `proof` | `response/data/proofs/<operationId>.<proofKind>.json` | data | no |
|
|
166
|
-
|
|
167
|
-
## Stops
|
|
168
|
-
|
|
169
|
-
| Code | Disposition |
|
|
170
|
-
| --- | --- |
|
|
171
|
-
| `INVALID_INPUT` | terminate |
|
|
172
|
-
| `SOURCE_DRIFT` | terminate |
|
|
173
|
-
| `NO_PROGRESS` | terminate |
|
|
174
|
-
| `CONTRACT_UNFROZEN` | terminate |
|
|
175
|
-
| `CONTRACT_WIDENED` | terminate |
|
|
176
|
-
| `BUSINESS_AUTHORITY_MISSING` | terminate |
|
|
177
|
-
| `OWNER_CONFLICT` | terminate |
|
|
178
|
-
| `PATTERN_UNBOUND` | terminate |
|
|
179
|
-
| `PROOF_UNAVAILABLE` | terminate |
|
|
180
|
-
|
|
181
|
-
## Next
|
|
182
|
-
|
|
183
|
-
| When | Operator |
|
|
184
|
-
| --- | --- |
|
|
185
|
-
| the contract is filled and the gates the change record names must run | `quality.verify` |
|
|
186
|
-
| the promise must be reconciled against the source that was delivered | `business.decide` |
|
|
187
|
-
| the contract is filled and a frontend surface must consume it | `frontend.direction.decide` |
|
|
188
|
-
| a file needing mutation lies outside the routed write ceiling | `workspace.bind` |
|
|
189
|
-
| a declared proof cannot be executed in this environment | `platform.operate` |
|
|
155
|
+
|
|
156
|
+
Under `mode = dry` step 3 projects the fill onto the declared paths without writing one of them, step
|
|
157
|
+
4 records that projection as the plan with a null commit and no after hash, steps 5 to 7 have nothing
|
|
158
|
+
to measure and produce nothing, and step 8 emits the receipt and the change record without a commit.
|
|
159
|
+
Under `apply` every step runs as written. The routed head is reverified immediately before the first
|
|
160
|
+
product write, so drift found there stops the branch before anything is written. Filling an operation writes the transport, the validation, the
|
|
161
|
+
authorization check, the data access, and the failure paths into the declared writer and the files the
|
|
162
|
+
change genuinely requires; it refuses loudly and early rather than dropping a case silently, raising
|
|
163
|
+
the exception the exception-identity pattern publishes before any row or external checkout is created.
|
|
164
|
+
When the outcome persists a workflow, session, cart, draft, or other snapshot, usability is enforced
|
|
165
|
+
again where it is read, reconciled server side, in stable order, with indexes remapped atomically and
|
|
166
|
+
an explicit terminal state when nothing actionable remains, and recorded as `SNAPSHOT_REVALIDATED`. A
|
|
167
|
+
resume begins again at step 1, reuses only unchanged fingerprinted observations, and consumes the exact
|
|
168
|
+
delta; an approved business decision arrives as a new authority fingerprint, because the same
|
|
169
|
+
fingerprint cannot yield a different answer.
|
|
170
|
+
|
|
171
|
+
## Outputs
|
|
172
|
+
|
|
173
|
+
| Kind | File | Type | Required |
|
|
174
|
+
| --- | --- | --- | --- |
|
|
175
|
+
| `backend-source-application` | `response/response.md` | md | yes |
|
|
176
|
+
| `changes` | `response/changes.md` | md | yes |
|
|
177
|
+
| `mutations` | `response/data/mutations.json` | data | yes |
|
|
178
|
+
| `conformance` | `response/data/conformance/<operationId>.<facet>.json` | data | no |
|
|
179
|
+
| `proof` | `response/data/proofs/<operationId>.<proofKind>.json` | data | no |
|
|
180
|
+
|
|
181
|
+
## Stops
|
|
182
|
+
|
|
183
|
+
| Code | Disposition |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `INVALID_INPUT` | terminate |
|
|
186
|
+
| `SOURCE_DRIFT` | terminate |
|
|
187
|
+
| `NO_PROGRESS` | terminate |
|
|
188
|
+
| `CONTRACT_UNFROZEN` | terminate |
|
|
189
|
+
| `CONTRACT_WIDENED` | terminate |
|
|
190
|
+
| `BUSINESS_AUTHORITY_MISSING` | terminate |
|
|
191
|
+
| `OWNER_CONFLICT` | terminate |
|
|
192
|
+
| `PATTERN_UNBOUND` | terminate |
|
|
193
|
+
| `PROOF_UNAVAILABLE` | terminate |
|
|
194
|
+
|
|
195
|
+
## Next
|
|
196
|
+
|
|
197
|
+
| When | Operator |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| the contract is filled and the gates the change record names must run | `quality.verify` |
|
|
200
|
+
| the promise must be reconciled against the source that was delivered | `business.decide` |
|
|
201
|
+
| the contract is filled and a frontend surface must consume it | `frontend.direction.decide` |
|
|
202
|
+
| a file needing mutation lies outside the routed write ceiling | `workspace.bind` |
|
|
203
|
+
| a declared proof cannot be executed in this environment | `platform.operate` |
|
|
204
|
+
| the plan was produced under mode dry and a person decides whether to pay for it | `user` |
|