@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.
Files changed (75) hide show
  1. package/INDEX.md +3 -1
  2. package/INDEX.vi.md +3 -1
  3. package/README.md +6 -1
  4. package/README.vi.md +6 -1
  5. package/knowledge/ui/presentation/INDEX.md +5 -0
  6. package/knowledge/ui/presentation/INDEX.vi.md +1 -0
  7. package/knowledge/ui/presentation/radius.md +183 -0
  8. package/knowledge/ui/presentation/radius.vi.md +182 -0
  9. package/operators/INDEX.md +5 -5
  10. package/operators/INDEX.vi.md +5 -5
  11. package/operators/architecture-decide/operator.md +27 -4
  12. package/operators/architecture-decide/operator.vi.md +23 -4
  13. package/operators/architecture-decide/self-test.mjs +18 -1
  14. package/operators/architecture-decide/validate.mjs +29 -1
  15. package/operators/backend-source-apply/operator.md +197 -182
  16. package/operators/backend-source-apply/operator.vi.md +190 -178
  17. package/operators/backend-source-apply/self-test.mjs +257 -257
  18. package/operators/backend-source-apply/validate.mjs +240 -240
  19. package/operators/business-decide/self-test.mjs +2 -1
  20. package/operators/business-decide/validate.mjs +3 -0
  21. package/operators/content-generate/self-test.mjs +1 -1
  22. package/operators/frontend-direction-decide/self-test.mjs +1 -1
  23. package/operators/frontend-presentation-resolve/operator.md +6 -0
  24. package/operators/frontend-presentation-resolve/operator.vi.md +6 -0
  25. package/operators/frontend-presentation-resolve/self-test.mjs +6 -5
  26. package/operators/frontend-presentation-resolve/validate.mjs +30 -6
  27. package/operators/frontend-source-apply/operator.md +3 -1
  28. package/operators/frontend-source-apply/operator.vi.md +3 -1
  29. package/operators/frontend-source-apply/self-test.mjs +1 -1
  30. package/operators/frontend-surface-audit/self-test.mjs +1 -1
  31. package/operators/git-publish/self-test.mjs +1 -1
  32. package/operators/platform-operate/self-test.mjs +1 -1
  33. package/operators/quality-verify/operator.md +5 -1
  34. package/operators/quality-verify/operator.vi.md +5 -1
  35. package/operators/quality-verify/self-test.mjs +15 -2
  36. package/operators/quality-verify/validate.mjs +21 -1
  37. package/operators/release-deploy/self-test.mjs +235 -235
  38. package/operators/uat-verify/operator.md +20 -6
  39. package/operators/uat-verify/operator.vi.md +21 -7
  40. package/operators/uat-verify/self-test.mjs +6 -5
  41. package/operators/uat-verify/validate.mjs +10 -4
  42. package/operators/workspace-bind/operator.md +18 -4
  43. package/operators/workspace-bind/operator.vi.md +16 -4
  44. package/operators/workspace-bind/self-test.mjs +3 -1
  45. package/operators/workspace-bind/validate.mjs +1 -0
  46. package/package.json +1 -1
  47. package/readiness/initialization/workspaces/local-route.schema.json +1 -1
  48. package/resources/orchestrator.json +14 -8
  49. package/routing.json +2 -1
  50. package/scripts/validate-request.mjs +12 -1
  51. package/scripts/validate-response.mjs +14 -3
  52. package/scripts/validate-step.mjs +2 -1
  53. package/scripts/validate-workflows.mjs +8 -1
  54. package/templates/kinds/architecture-decision.contract.json +1 -0
  55. package/templates/kinds/architecture-decision.skeleton.md +6 -0
  56. package/templates/kinds/backend-source-application.contract.json +1 -1
  57. package/templates/kinds/backend-source-application.skeleton.md +1 -1
  58. package/templates/kinds/frontend-presentation-resolution.contract.json +1 -1
  59. package/templates/kinds/frontend-presentation-resolution.skeleton.md +1 -0
  60. package/templates/kinds/mutations.schema.json +65 -65
  61. package/templates/kinds/route.schema.json +4 -2
  62. package/templates/kinds/stack-model.schema.json +23 -1
  63. package/templates/kinds/workspace-route-binding.contract.json +2 -2
  64. package/templates/step/response.schema.json +10 -0
  65. package/templates/step/state.schema.json +205 -0
  66. package/workflows/README.md +3 -0
  67. package/workflows/README.vi.md +3 -0
  68. package/workflows/backend-feature.json +26 -5
  69. package/workflows/content-unit.json +4 -1
  70. package/workflows/frontend-new-surface.json +15 -5
  71. package/workflows/frontend-reconstruct.json +10 -4
  72. package/workflows/frontend-refine.json +10 -4
  73. package/workflows/frontend-with-uat.json +15 -3
  74. package/workflows/full-feature.json +31 -8
  75. 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 giữ | có |
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` | `response/data/stack-model.json` | `DATA_OWNERSHIP_UNASSIGNED`, `COMPATIBILITY_UNVERIFIED` |
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 and restates them in
16
- `response/data/mutations.json`. Three prohibitions carry that, and each is enforced rather than
17
- advised. An operation, writer, store, transaction, migration, or event outside the contract is
18
- `CONTRACT_WIDENED`, returned to the contract owner before any product write. A file outside the
19
- mutable ceiling is `OWNER_CONFLICT`, even when the change there would be one line. A convention no
20
- bound sibling pattern publishes is refused and recorded as `NEW_CONVENTION_REFUSED`, while an aspect
21
- with no pattern at all is `PATTERN_UNBOUND`. Discovering mid-implementation that the outcome needs a
22
- wider boundary is the expected way this operator ends, not a failure of nerve: the contract is
23
- reopened by its owner and the same outcome is implemented again against the new fingerprint. Reaching
24
- outside the list is not a smaller change than reopening the contract; it is the same change made
25
- without a record.
26
-
27
- ## The person's branch is never written
28
-
29
- This operator never writes on the branch a person has checked out. The orchestrator prepares a
30
- dedicated git worktree of the routed checkout on the session branch `session/<sessionId>`, cut from
31
- the frozen head, and step 3 writes there and nowhere else, under an exclusive lease on
32
- `@workspaces/be`. The final step commits the whole declared write set once, records that sha in
33
- `response.json.commits`, and names the same sha in `response/data/mutations.json` as `commit` beside
34
- the `base` it started from and the `branch` it lives on; `response/changes.md` states the same move in
35
- its Binding row, `@workspaces/be` at `<base>` `<sha>` on `session/<sessionId>`. One commit, because
36
- a step whose work arrives as several commits cannot be pinned by the next step's request, and an
37
- uncommitted write cannot be pinned at all. Nothing is pushed and nothing is merged here: `git.publish`
38
- merges the session branch into the target branch, and it is the only operator that talks to a remote.
39
-
40
- ## Dry mode writes the plan, not the tree
41
-
42
- `mode` decides whether this run touches the checkout at all. Under `apply` the operator fills the
43
- contract, commits once, and everything below holds as written. Under `dry` it does the same reading,
44
- the same binding and the same projection, then stops after the plan: `response/data/mutations.json`
45
- carries the operations it would fill and the files it would touch with `commit` null and no after
46
- hash, `response.json` records no commit, and not one byte reaches `@workspaces/be`. The branch still
47
- ends `done`, because a plan honestly produced is a finished answer to a question about a plan; its
48
- `changes.md` lists every planned path as `unchanged`, which is what the working tree actually shows,
49
- and names the change it would have made in `Why`. A dry run measures nothing, so it carries no
50
- conformance record and no proof record: a facet cannot be measured on code that was never written,
51
- and a plan that shipped verdicts would be indistinguishable from an implementation. That is also why
52
- a dry run can never be the run that satisfies the contract it is a way to read the write set before
53
- paying for it, not a cheaper way to apply it.
54
-
55
- ## The backend never invents business behaviour
56
-
57
- Every operation cites the approved decisions it implements. When the code reaches a point where the
58
- answer depends on a business rule nobody approved, the branch stops with `BUSINESS_AUTHORITY_MISSING`
59
- and names the open question. It does not pick the lenient reading, mirror what a neighbouring feature
60
- happens to do, or choose whichever branch makes the test go green. This is the most load-bearing rule
61
- in the operator, because a guessed business rule that passes its own test is indistinguishable from an
62
- approved one once it ships. An implemented receipt therefore cannot carry a `BUSINESS_QUESTION_RAISED`
63
- finding: raising the question and implementing anyway is the exact contradiction the check exists to
64
- catch.
65
-
66
- ## Sibling patterns are the only source of convention
67
-
68
- The bound patterns name one family per aspect, and the implementation mirrors the family the codebase
69
- already publishes rather than the one it remembers: command handlers in the family the mutation layer
70
- already uses, exceptions derived from the published exception identity, entity access through the
71
- injected primary entity manager, migrations under the primary datasource. Two families bound for one
72
- aspect means no family is bound, and guessing the family from memory is how a second house style
73
- enters a codebase unnoticed.
74
-
75
- ## Conformance is measured, not asserted
76
-
77
- A conformance record without evidence is a sentence about the code, and a sentence cannot contradict
78
- the code. Each declared facet of each operation gets its own file,
79
- `response/data/conformance/<operationId>.<facet>.json`, so a facet nobody measured is a missing file
80
- rather than a missing line inside a file that still looks complete. The evidence is what a later
81
- reader uses to disagree with this receipt, so it is required for every facet including the ones that
82
- passed. The same reasoning makes a proof carry its command, its exit code and its output in
83
- `response/data/proofs/<operationId>.<kind>.json`: the command says what was run and the result says
84
- what came back, and either one alone can be written by someone who ran nothing. A proof that could not
85
- run never becomes an assertion that the behaviour is fine, and a failed proof blocks the receipt
86
- rather than being reclassified. Each touched file carries one change record with its kind and its
87
- before and after hashes, because a modified file whose two hashes agree records a mutation that did
88
- not happen.
89
-
90
- ## Boundary
91
-
92
- The operator writes product source only inside the mutable file ceiling, only inside the session
93
- branch worktree of `@workspaces/be`, and writes everything else into `response/` of its own branch:
94
- `response.md`, `response/changes.md`, `response/data/mutations.json`, one conformance record per
95
- declared facet, one proof record per declared proof, and `response.json`. It never adds an operation,
96
- writer, store, transaction, migration, or event the frozen contract does not carry, decides a business
97
- rule the approved authority does not state, introduces a convention no bound sibling pattern
98
- publishes, weakens, skips, suppresses, or substitutes a declared proof to make a run go green, edits
99
- the contract, the business authority, or a file outside the mutable ceiling, commits more than once,
100
- writes on the person's checked-out branch, pushes, merges, or tags anything, claims conformance
101
- without naming the evidence that measured it, or records a quality, visual, or UAT verdict; those are
102
- other jobs with their own gates.
103
-
104
- ## Context
105
-
106
- | Alias | Bind | Required |
107
- | --- | --- | --- |
108
- | `@worktrees/businesses/<featureId>` | the published business head, the only source of business behaviour | yes |
109
- | `@knowledge/patterns/be` | the sibling families this change mirrors, one per aspect; the only source of valid conventions | yes |
110
- | `@workspaces/be` | the routed backend checkout at the frozen head, written only on its session branch worktree | yes |
111
-
112
- ## Inputs
113
-
114
- | Kind | From | Required |
115
- | --- | --- | --- |
116
- | `architecture-decision` | `architecture.decide`; the frozen mutation contract the implementation fills and may not widen | yes |
117
- | `backend-source-application` | a prior run of `backend.source.apply` for the same outcome; regression history, absent on the first run | no |
118
-
119
- ## Requirements
120
-
121
- | Field | Type | Default | Ask |
122
- | --- | --- | --- | --- |
123
- | `featureId` | id | | The feature whose published business head decides this behaviour |
124
- | `outcome` | prompt | — | The one thing being implemented, in the person's words |
125
- | `mutableFileRefs` | list | — | The only files product source may be written into |
126
- | `mode` | choice | apply | `apply` fills the contract and commits, `dry` emits the plan and writes nothing |
127
- | `resume` | token | null | The blocked branch's token when re-entering after a stop |
128
-
129
- ## Steps
130
-
131
- | # | Step | Params | Reads | Writes | Stops with |
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, @knowledge/patterns/be one pattern per aspect | — | `CONTRACT_UNFROZEN`, `BUSINESS_AUTHORITY_MISSING`, `PATTERN_UNBOUND` |
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` |