agent-sdd 1.0.3

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 (304) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1028 -0
  3. package/README.ru.md +1046 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.js +867 -0
  6. package/dist/features/approve/adapters/inbound/CliApproveHandler.d.ts +17 -0
  7. package/dist/features/approve/adapters/inbound/CliApproveHandler.js +108 -0
  8. package/dist/features/approve/adapters/outbound/NodeApproveFileSystem.d.ts +8 -0
  9. package/dist/features/approve/adapters/outbound/NodeApproveFileSystem.js +147 -0
  10. package/dist/features/approve/adapters/outbound/NodePlanFileWriter.d.ts +6 -0
  11. package/dist/features/approve/adapters/outbound/NodePlanFileWriter.js +92 -0
  12. package/dist/features/approve/adapters/outbound/SystemApproveClock.d.ts +4 -0
  13. package/dist/features/approve/adapters/outbound/SystemApproveClock.js +5 -0
  14. package/dist/features/approve/application/ApplyApproval.d.ts +19 -0
  15. package/dist/features/approve/application/ApplyApproval.js +30 -0
  16. package/dist/features/approve/application/WriteAttestation.d.ts +19 -0
  17. package/dist/features/approve/application/WriteAttestation.js +23 -0
  18. package/dist/features/approve/domain/ApproveRequest.d.ts +24 -0
  19. package/dist/features/approve/domain/ApproveRequest.js +24 -0
  20. package/dist/features/approve/domain/Rewrite.d.ts +1 -0
  21. package/dist/features/approve/domain/Rewrite.js +6 -0
  22. package/dist/features/approve/ports/inbound/ApproveCommand.d.ts +10 -0
  23. package/dist/features/approve/ports/inbound/ApproveCommand.js +1 -0
  24. package/dist/features/approve/ports/outbound/ApproveClock.d.ts +3 -0
  25. package/dist/features/approve/ports/outbound/ApproveClock.js +1 -0
  26. package/dist/features/approve/ports/outbound/ApproveConfigPort.d.ts +4 -0
  27. package/dist/features/approve/ports/outbound/ApproveConfigPort.js +1 -0
  28. package/dist/features/approve/ports/outbound/ApproveFileSystem.d.ts +8 -0
  29. package/dist/features/approve/ports/outbound/ApproveFileSystem.js +1 -0
  30. package/dist/features/approve/ports/outbound/PlanFileWriter.d.ts +13 -0
  31. package/dist/features/approve/ports/outbound/PlanFileWriter.js +1 -0
  32. package/dist/features/check/adapters/inbound/CliCheckHandler.d.ts +8 -0
  33. package/dist/features/check/adapters/inbound/CliCheckHandler.js +62 -0
  34. package/dist/features/check/adapters/outbound/ChildProcessCheckGit.d.ts +8 -0
  35. package/dist/features/check/adapters/outbound/ChildProcessCheckGit.js +112 -0
  36. package/dist/features/check/adapters/outbound/NodeCheckFileReader.d.ts +7 -0
  37. package/dist/features/check/adapters/outbound/NodeCheckFileReader.js +44 -0
  38. package/dist/features/check/application/CheckBaseline.d.ts +28 -0
  39. package/dist/features/check/application/CheckBaseline.js +54 -0
  40. package/dist/features/check/domain/BaselineComparison.d.ts +1 -0
  41. package/dist/features/check/domain/BaselineComparison.js +2 -0
  42. package/dist/features/check/ports/inbound/CheckCommand.d.ts +4 -0
  43. package/dist/features/check/ports/inbound/CheckCommand.js +1 -0
  44. package/dist/features/check/ports/outbound/CheckConfigPort.d.ts +4 -0
  45. package/dist/features/check/ports/outbound/CheckConfigPort.js +1 -0
  46. package/dist/features/check/ports/outbound/CheckGitPort.d.ts +7 -0
  47. package/dist/features/check/ports/outbound/CheckGitPort.js +1 -0
  48. package/dist/features/check/ports/outbound/CheckSpecPort.d.ts +9 -0
  49. package/dist/features/check/ports/outbound/CheckSpecPort.js +1 -0
  50. package/dist/features/doctor/adapters/inbound/CliDoctorHandler.d.ts +8 -0
  51. package/dist/features/doctor/adapters/inbound/CliDoctorHandler.js +77 -0
  52. package/dist/features/doctor/adapters/outbound/NodeRegistryReader.d.ts +5 -0
  53. package/dist/features/doctor/adapters/outbound/NodeRegistryReader.js +40 -0
  54. package/dist/features/doctor/application/RunDoctor.d.ts +30 -0
  55. package/dist/features/doctor/application/RunDoctor.js +78 -0
  56. package/dist/features/doctor/domain/RegistryRow.d.ts +23 -0
  57. package/dist/features/doctor/domain/RegistryRow.js +114 -0
  58. package/dist/features/doctor/domain/SemverRange.d.ts +7 -0
  59. package/dist/features/doctor/domain/SemverRange.js +82 -0
  60. package/dist/features/doctor/ports/inbound/DoctorCommand.d.ts +8 -0
  61. package/dist/features/doctor/ports/inbound/DoctorCommand.js +1 -0
  62. package/dist/features/doctor/ports/outbound/RegistryReader.d.ts +12 -0
  63. package/dist/features/doctor/ports/outbound/RegistryReader.js +1 -0
  64. package/dist/features/finalize/adapters/inbound/CliFinalizeHandler.d.ts +8 -0
  65. package/dist/features/finalize/adapters/inbound/CliFinalizeHandler.js +80 -0
  66. package/dist/features/finalize/adapters/outbound/NodeFinalizeFileSystem.d.ts +11 -0
  67. package/dist/features/finalize/adapters/outbound/NodeFinalizeFileSystem.js +167 -0
  68. package/dist/features/finalize/adapters/outbound/NodePlanRepo.d.ts +7 -0
  69. package/dist/features/finalize/adapters/outbound/NodePlanRepo.js +82 -0
  70. package/dist/features/finalize/adapters/outbound/SystemFinalizeClock.d.ts +4 -0
  71. package/dist/features/finalize/adapters/outbound/SystemFinalizeClock.js +5 -0
  72. package/dist/features/finalize/application/RunFinalize.d.ts +34 -0
  73. package/dist/features/finalize/application/RunFinalize.js +98 -0
  74. package/dist/features/finalize/domain/ValidateFinalizeGraph.d.ts +9 -0
  75. package/dist/features/finalize/domain/ValidateFinalizeGraph.js +86 -0
  76. package/dist/features/finalize/ports/inbound/FinalizeCommand.d.ts +7 -0
  77. package/dist/features/finalize/ports/inbound/FinalizeCommand.js +1 -0
  78. package/dist/features/finalize/ports/outbound/FinalizeClock.d.ts +3 -0
  79. package/dist/features/finalize/ports/outbound/FinalizeClock.js +1 -0
  80. package/dist/features/finalize/ports/outbound/FinalizeConfigPort.d.ts +4 -0
  81. package/dist/features/finalize/ports/outbound/FinalizeConfigPort.js +1 -0
  82. package/dist/features/finalize/ports/outbound/FinalizeFileSystem.d.ts +14 -0
  83. package/dist/features/finalize/ports/outbound/FinalizeFileSystem.js +1 -0
  84. package/dist/features/finalize/ports/outbound/PlanRepo.d.ts +21 -0
  85. package/dist/features/finalize/ports/outbound/PlanRepo.js +1 -0
  86. package/dist/features/install/adapters/inbound/CliInstallHandler.d.ts +8 -0
  87. package/dist/features/install/adapters/inbound/CliInstallHandler.js +54 -0
  88. package/dist/features/install/adapters/outbound/NodeInstallSource.d.ts +7 -0
  89. package/dist/features/install/adapters/outbound/NodeInstallSource.js +24 -0
  90. package/dist/features/install/adapters/outbound/NodeInstallTargetFs.d.ts +7 -0
  91. package/dist/features/install/adapters/outbound/NodeInstallTargetFs.js +30 -0
  92. package/dist/features/install/application/InstallRules.d.ts +10 -0
  93. package/dist/features/install/application/InstallRules.js +73 -0
  94. package/dist/features/install/domain/InstallPlan.d.ts +27 -0
  95. package/dist/features/install/domain/InstallPlan.js +168 -0
  96. package/dist/features/install/domain/InstallResult.d.ts +23 -0
  97. package/dist/features/install/domain/InstallResult.js +1 -0
  98. package/dist/features/install/domain/InstallTarget.d.ts +6 -0
  99. package/dist/features/install/domain/InstallTarget.js +7 -0
  100. package/dist/features/install/domain/ManagedBlock.d.ts +3 -0
  101. package/dist/features/install/domain/ManagedBlock.js +20 -0
  102. package/dist/features/install/domain/RuleManifest.d.ts +17 -0
  103. package/dist/features/install/domain/RuleManifest.js +69 -0
  104. package/dist/features/install/domain/SettingsMerge.d.ts +5 -0
  105. package/dist/features/install/domain/SettingsMerge.js +43 -0
  106. package/dist/features/install/ports/inbound/InstallCommand.d.ts +10 -0
  107. package/dist/features/install/ports/inbound/InstallCommand.js +1 -0
  108. package/dist/features/install/ports/outbound/InstallSource.d.ts +4 -0
  109. package/dist/features/install/ports/outbound/InstallSource.js +1 -0
  110. package/dist/features/install/ports/outbound/InstallTargetFs.d.ts +6 -0
  111. package/dist/features/install/ports/outbound/InstallTargetFs.js +1 -0
  112. package/dist/features/lint/adapters/inbound/CliLintHandler.d.ts +8 -0
  113. package/dist/features/lint/adapters/inbound/CliLintHandler.js +61 -0
  114. package/dist/features/lint/adapters/outbound/NodeLintFileReader.d.ts +7 -0
  115. package/dist/features/lint/adapters/outbound/NodeLintFileReader.js +165 -0
  116. package/dist/features/lint/application/RunLint.d.ts +10 -0
  117. package/dist/features/lint/application/RunLint.js +100 -0
  118. package/dist/features/lint/domain/Diagnostic.d.ts +1 -0
  119. package/dist/features/lint/domain/Diagnostic.js +2 -0
  120. package/dist/features/lint/domain/Record.d.ts +1 -0
  121. package/dist/features/lint/domain/Record.js +5 -0
  122. package/dist/features/lint/domain/Rules.d.ts +1 -0
  123. package/dist/features/lint/domain/Rules.js +2 -0
  124. package/dist/features/lint/domain/SpecParser.d.ts +1 -0
  125. package/dist/features/lint/domain/SpecParser.js +2 -0
  126. package/dist/features/lint/ports/inbound/LintCommand.d.ts +4 -0
  127. package/dist/features/lint/ports/inbound/LintCommand.js +1 -0
  128. package/dist/features/lint/ports/outbound/LintConfigPort.d.ts +4 -0
  129. package/dist/features/lint/ports/outbound/LintConfigPort.js +1 -0
  130. package/dist/features/lint/ports/outbound/LintFileReader.d.ts +10 -0
  131. package/dist/features/lint/ports/outbound/LintFileReader.js +1 -0
  132. package/dist/features/plan/adapters/inbound/CliPlanShowHandler.d.ts +8 -0
  133. package/dist/features/plan/adapters/inbound/CliPlanShowHandler.js +73 -0
  134. package/dist/features/plan/adapters/outbound/NodePlanReader.d.ts +7 -0
  135. package/dist/features/plan/adapters/outbound/NodePlanReader.js +68 -0
  136. package/dist/features/plan/application/ShowPlan.d.ts +7 -0
  137. package/dist/features/plan/application/ShowPlan.js +4 -0
  138. package/dist/features/plan/ports/inbound/PlanShowCommand.d.ts +7 -0
  139. package/dist/features/plan/ports/inbound/PlanShowCommand.js +1 -0
  140. package/dist/features/plan/ports/outbound/PlanConfigPort.d.ts +4 -0
  141. package/dist/features/plan/ports/outbound/PlanConfigPort.js +1 -0
  142. package/dist/features/plan/ports/outbound/PlanReader.d.ts +19 -0
  143. package/dist/features/plan/ports/outbound/PlanReader.js +1 -0
  144. package/dist/features/ready/adapters/inbound/CliReadyHandler.d.ts +8 -0
  145. package/dist/features/ready/adapters/inbound/CliReadyHandler.js +79 -0
  146. package/dist/features/ready/adapters/outbound/ChildProcessReadyGit.d.ts +9 -0
  147. package/dist/features/ready/adapters/outbound/ChildProcessReadyGit.js +113 -0
  148. package/dist/features/ready/adapters/outbound/NodeReadyFileSystem.d.ts +8 -0
  149. package/dist/features/ready/adapters/outbound/NodeReadyFileSystem.js +159 -0
  150. package/dist/features/ready/application/RunReady.d.ts +16 -0
  151. package/dist/features/ready/application/RunReady.js +572 -0
  152. package/dist/features/ready/domain/AggregatedRules.d.ts +16 -0
  153. package/dist/features/ready/domain/AggregatedRules.js +42 -0
  154. package/dist/features/ready/domain/MarkerParser.d.ts +17 -0
  155. package/dist/features/ready/domain/MarkerParser.js +108 -0
  156. package/dist/features/ready/domain/PartitionResolver.d.ts +1 -0
  157. package/dist/features/ready/domain/PartitionResolver.js +5 -0
  158. package/dist/features/ready/domain/ReadyInput.d.ts +6 -0
  159. package/dist/features/ready/domain/ReadyInput.js +1 -0
  160. package/dist/features/ready/domain/ReadyViolation.d.ts +38 -0
  161. package/dist/features/ready/domain/ReadyViolation.js +19 -0
  162. package/dist/features/ready/domain/Rules.d.ts +22 -0
  163. package/dist/features/ready/domain/Rules.js +243 -0
  164. package/dist/features/ready/domain/SpecDiff.d.ts +33 -0
  165. package/dist/features/ready/domain/SpecDiff.js +321 -0
  166. package/dist/features/ready/ports/inbound/ReadyCommand.d.ts +4 -0
  167. package/dist/features/ready/ports/inbound/ReadyCommand.js +1 -0
  168. package/dist/features/ready/ports/outbound/ReadyConfigPort.d.ts +4 -0
  169. package/dist/features/ready/ports/outbound/ReadyConfigPort.js +1 -0
  170. package/dist/features/ready/ports/outbound/ReadyFileReader.d.ts +12 -0
  171. package/dist/features/ready/ports/outbound/ReadyFileReader.js +1 -0
  172. package/dist/features/ready/ports/outbound/ReadyGitPort.d.ts +10 -0
  173. package/dist/features/ready/ports/outbound/ReadyGitPort.js +5 -0
  174. package/dist/features/record/adapters/inbound/CliRecordHandler.d.ts +10 -0
  175. package/dist/features/record/adapters/inbound/CliRecordHandler.js +111 -0
  176. package/dist/features/record/adapters/outbound/NodeRecordFileSystem.d.ts +9 -0
  177. package/dist/features/record/adapters/outbound/NodeRecordFileSystem.js +152 -0
  178. package/dist/features/record/application/AddRecord.d.ts +11 -0
  179. package/dist/features/record/application/AddRecord.js +84 -0
  180. package/dist/features/record/application/GetRecord.d.ts +8 -0
  181. package/dist/features/record/application/GetRecord.js +22 -0
  182. package/dist/features/record/application/ListRecords.d.ts +9 -0
  183. package/dist/features/record/application/ListRecords.js +24 -0
  184. package/dist/features/record/application/SetRecord.d.ts +11 -0
  185. package/dist/features/record/application/SetRecord.js +68 -0
  186. package/dist/features/record/domain/RecordBody.d.ts +12 -0
  187. package/dist/features/record/domain/RecordBody.js +66 -0
  188. package/dist/features/record/domain/RecordPartition.d.ts +1 -0
  189. package/dist/features/record/domain/RecordPartition.js +7 -0
  190. package/dist/features/record/domain/RecordSlice.d.ts +7 -0
  191. package/dist/features/record/domain/RecordSlice.js +1 -0
  192. package/dist/features/record/domain/RecordSummary.d.ts +11 -0
  193. package/dist/features/record/domain/RecordSummary.js +13 -0
  194. package/dist/features/record/domain/RecordWrite.d.ts +14 -0
  195. package/dist/features/record/domain/RecordWrite.js +8 -0
  196. package/dist/features/record/ports/inbound/RecordCommand.d.ts +19 -0
  197. package/dist/features/record/ports/inbound/RecordCommand.js +1 -0
  198. package/dist/features/record/ports/outbound/RecordConfigPort.d.ts +4 -0
  199. package/dist/features/record/ports/outbound/RecordConfigPort.js +1 -0
  200. package/dist/features/record/ports/outbound/RecordFileReader.d.ts +10 -0
  201. package/dist/features/record/ports/outbound/RecordFileReader.js +1 -0
  202. package/dist/features/record/ports/outbound/RecordFileWriter.d.ts +6 -0
  203. package/dist/features/record/ports/outbound/RecordFileWriter.js +1 -0
  204. package/dist/features/refresh/adapters/inbound/CliRefreshHandler.d.ts +8 -0
  205. package/dist/features/refresh/adapters/inbound/CliRefreshHandler.js +24 -0
  206. package/dist/features/refresh/adapters/outbound/ChildProcessRefreshGit.d.ts +8 -0
  207. package/dist/features/refresh/adapters/outbound/ChildProcessRefreshGit.js +118 -0
  208. package/dist/features/refresh/adapters/outbound/NodeRefreshFileReader.d.ts +7 -0
  209. package/dist/features/refresh/adapters/outbound/NodeRefreshFileReader.js +44 -0
  210. package/dist/features/refresh/adapters/outbound/SystemRefreshClock.d.ts +4 -0
  211. package/dist/features/refresh/adapters/outbound/SystemRefreshClock.js +5 -0
  212. package/dist/features/refresh/application/BuildRefreshStubs.d.ts +25 -0
  213. package/dist/features/refresh/application/BuildRefreshStubs.js +43 -0
  214. package/dist/features/refresh/domain/DiffStubs.d.ts +24 -0
  215. package/dist/features/refresh/domain/DiffStubs.js +81 -0
  216. package/dist/features/refresh/domain/Footprint.d.ts +14 -0
  217. package/dist/features/refresh/domain/Footprint.js +45 -0
  218. package/dist/features/refresh/ports/inbound/RefreshCommand.d.ts +4 -0
  219. package/dist/features/refresh/ports/inbound/RefreshCommand.js +1 -0
  220. package/dist/features/refresh/ports/outbound/RefreshClockPort.d.ts +3 -0
  221. package/dist/features/refresh/ports/outbound/RefreshClockPort.js +1 -0
  222. package/dist/features/refresh/ports/outbound/RefreshConfigPort.d.ts +4 -0
  223. package/dist/features/refresh/ports/outbound/RefreshConfigPort.js +1 -0
  224. package/dist/features/refresh/ports/outbound/RefreshGitPort.d.ts +7 -0
  225. package/dist/features/refresh/ports/outbound/RefreshGitPort.js +1 -0
  226. package/dist/features/refresh/ports/outbound/RefreshSpecPort.d.ts +9 -0
  227. package/dist/features/refresh/ports/outbound/RefreshSpecPort.js +1 -0
  228. package/dist/features/report/adapters/inbound/CliReportHandler.d.ts +8 -0
  229. package/dist/features/report/adapters/inbound/CliReportHandler.js +35 -0
  230. package/dist/features/report/adapters/outbound/NodeReportFileSystem.d.ts +7 -0
  231. package/dist/features/report/adapters/outbound/NodeReportFileSystem.js +128 -0
  232. package/dist/features/report/application/RunReport.d.ts +19 -0
  233. package/dist/features/report/application/RunReport.js +161 -0
  234. package/dist/features/report/ports/inbound/ReportCommand.d.ts +8 -0
  235. package/dist/features/report/ports/inbound/ReportCommand.js +1 -0
  236. package/dist/features/report/ports/outbound/ReportConfigPort.d.ts +4 -0
  237. package/dist/features/report/ports/outbound/ReportConfigPort.js +1 -0
  238. package/dist/features/report/ports/outbound/ReportFileReader.d.ts +7 -0
  239. package/dist/features/report/ports/outbound/ReportFileReader.js +1 -0
  240. package/dist/features/token/adapters/inbound/CliTokenHandler.d.ts +8 -0
  241. package/dist/features/token/adapters/inbound/CliTokenHandler.js +53 -0
  242. package/dist/features/token/adapters/outbound/ChildProcessTokenGit.d.ts +8 -0
  243. package/dist/features/token/adapters/outbound/ChildProcessTokenGit.js +112 -0
  244. package/dist/features/token/adapters/outbound/NodeTokenConfigReader.d.ts +5 -0
  245. package/dist/features/token/adapters/outbound/NodeTokenConfigReader.js +41 -0
  246. package/dist/features/token/application/ComputeToken.d.ts +18 -0
  247. package/dist/features/token/application/ComputeToken.js +27 -0
  248. package/dist/features/token/ports/inbound/TokenCommand.d.ts +4 -0
  249. package/dist/features/token/ports/inbound/TokenCommand.js +1 -0
  250. package/dist/features/token/ports/outbound/TokenConfigPort.d.ts +4 -0
  251. package/dist/features/token/ports/outbound/TokenConfigPort.js +1 -0
  252. package/dist/features/token/ports/outbound/TokenGitPort.d.ts +7 -0
  253. package/dist/features/token/ports/outbound/TokenGitPort.js +1 -0
  254. package/dist/shared/domain/AgentBlocklist.d.ts +5 -0
  255. package/dist/shared/domain/AgentBlocklist.js +28 -0
  256. package/dist/shared/domain/BoundaryReachability.d.ts +5 -0
  257. package/dist/shared/domain/BoundaryReachability.js +71 -0
  258. package/dist/shared/domain/CheckOutcome.d.ts +10 -0
  259. package/dist/shared/domain/CheckOutcome.js +7 -0
  260. package/dist/shared/domain/CliOutput.d.ts +10 -0
  261. package/dist/shared/domain/CliOutput.js +29 -0
  262. package/dist/shared/domain/Config.d.ts +29 -0
  263. package/dist/shared/domain/Config.js +201 -0
  264. package/dist/shared/domain/DiagnosticRegistry.d.ts +8 -0
  265. package/dist/shared/domain/DiagnosticRegistry.js +71 -0
  266. package/dist/shared/domain/Errors.d.ts +12 -0
  267. package/dist/shared/domain/Errors.js +23 -0
  268. package/dist/shared/domain/GlobMatch.d.ts +2 -0
  269. package/dist/shared/domain/GlobMatch.js +58 -0
  270. package/dist/shared/domain/LintReport.d.ts +16 -0
  271. package/dist/shared/domain/LintReport.js +11 -0
  272. package/dist/shared/domain/LintRules.d.ts +67 -0
  273. package/dist/shared/domain/LintRules.js +956 -0
  274. package/dist/shared/domain/PartitionGrammar.d.ts +4 -0
  275. package/dist/shared/domain/PartitionGrammar.js +16 -0
  276. package/dist/shared/domain/PlanFile.d.ts +28 -0
  277. package/dist/shared/domain/PlanFile.js +112 -0
  278. package/dist/shared/domain/Scope.d.ts +1 -0
  279. package/dist/shared/domain/Scope.js +3 -0
  280. package/dist/shared/domain/SpecApprovalRewrite.d.ts +23 -0
  281. package/dist/shared/domain/SpecApprovalRewrite.js +254 -0
  282. package/dist/shared/domain/SpecBlocks.d.ts +12 -0
  283. package/dist/shared/domain/SpecBlocks.js +96 -0
  284. package/dist/shared/domain/SpecRecord.d.ts +17 -0
  285. package/dist/shared/domain/SpecRecord.js +208 -0
  286. package/dist/shared/domain/TemplateFieldMetadata.d.ts +2 -0
  287. package/dist/shared/domain/TemplateFieldMetadata.js +177 -0
  288. package/dist/shared/domain/Token.d.ts +2 -0
  289. package/dist/shared/domain/Token.js +5 -0
  290. package/dist/shared/domain/WeaselWords.d.ts +3 -0
  291. package/dist/shared/domain/WeaselWords.js +32 -0
  292. package/package.json +71 -0
  293. package/rules/enforcement_registry.md +126 -0
  294. package/rules/hooks/sdd-lint-reminder.sh +33 -0
  295. package/rules/hooks/sdd-spec-read-guard.sh +73 -0
  296. package/rules/manifest.json +15 -0
  297. package/rules/review-sdd.md +9 -0
  298. package/rules/sdd-cli-usage.md +91 -0
  299. package/rules/skills/spec-driven-development/SKILL.md +554 -0
  300. package/rules/skills/spec-driven-development/data/weasel-words.json +22 -0
  301. package/rules/spec-driven-development.md +69 -0
  302. package/rules/tdd-sdd.md +127 -0
  303. package/rules/workflow-sdd.md +56 -0
  304. package/schema/sdd.config.schema.json +104 -0
@@ -0,0 +1,554 @@
1
+ ---
2
+ name: spec-driven-development
3
+ description: Full reference for spec-driven development on brownfield code. Use when writing, drafting, reviewing, auditing, or evolving a specification before code; when the user says spec-first, spec-driven, the spec is the source of truth, write a spec, draft a specification, review my spec, is this spec valid, spec out X, напиши спеку, составь спецификацию, спека первой, написать ТЗ, or описать в спеке. Covers typed normative templates, three gates, Brownfield baseline, Discovery scope, Delta, Migration, Surface semver, Policy, Contract, Invariant, Open-Q, ASSUMPTION, test obligations, and what belongs in the spec versus what the agent decides.
4
+ ---
5
+
6
+ # Specification Authoring Rules for Spec-Driven Development
7
+
8
+ This document is the working manual for an agent that authors a specification against existing code (brownfield) and then maintains it as the single source of truth for downstream code generation. Every rule is operational: each one can be checked mechanically or as an explicit item in spec code review.
9
+
10
+ Foundational principle: **the specification is the single source of truth. Any change in system behavior appears in the spec first; only then is the code-gen agent invoked.** Behavior found in code but absent from the spec is never silently legitimized: it is either lifted into a normative element through `Discovery scope`/`Open-Q` via the standard process, or explicitly marked as `unmodeled` and never silently deleted or rewritten.
11
+
12
+ When to use this skill:
13
+ - the author writes a new spec or revises an existing one and must verify it is ready to hand off to a code-gen agent;
14
+ - the agent runs read-only reconnaissance on brownfield code and produces a `Brownfield baseline`;
15
+ - there is a dispute about which template fits a particular statement (Contract vs Invariant vs Policy vs Migration), which gate must pass first, or why the agent halted instead of implementing;
16
+ - a patch / PR needs to be checked against one of the silent invariants (semver cascade, Migration cycle, self-approval, baseline freshness).
17
+
18
+ For day-to-day use the condensed rule in `rules/spec-driven-development.md` is enough (the global CLAUDE.md imports it automatically). This skill is the full reference with tables and rationale, consulted when in doubt.
19
+
20
+ ---
21
+
22
+ ## 0. Lifecycle and the three gates
23
+
24
+ Spec and implementation are checked by three independent CI gates. **All gates run per `Partition`** (see §13), not "across the whole repository", otherwise any drift blocks everyone.
25
+
26
+ - **`baseline-valid`** — verifies that the partition's `Discovery scope` covers declared entrypoints / datasets / flags / tenants / environments / generated artifacts; every `coverage_evidence` entry references a machine-readable artefact (entrypoint registry, code-skeleton dump, IaC inventory, schema registry snapshot, DB introspection); the baseline contains no contradictory descriptions of the same as-is ID; the `freshness_token` (hash of recon input sources) matches the current state. **Every `Delta`/`Migration` MUST carry a `baseline_version` field pinning it to a specific baseline revision.** A `freshness_token` mismatch flips the baseline to `stale` but **does NOT block authoring of new `Delta`/`Open-Q`** against the pinned `baseline_version` — it only blocks the transition of those `Delta`/`Migration` into `implementation-valid` until rebase. Recovery from `stale` goes through **`baseline_refresh`**, which MUST emit a machine-readable `diff(baseline_old, baseline_new)` and **automatically open a `Delta` stub or `Open-Q` for every change crossing the footprint of an already-normative ID** — otherwise refresh becomes a back-door for silent removal of normative behavior.
27
+ - **`spec-valid`** — blocks the code-gen agent. Verifies completeness, machine-readable structure, fields populated per the `Template Requiredness Matrix` (§14), a `Test obligation` on every normative ID, no unresolved `Open-Q.blocking=yes`, no weasel words in normative sections, correct semver markup of the per-`Surface` diff, an `approval_record` on every `approved`/`deprecated`/`removed` ID, and a hard ban on self-approval by the code-gen agent.
28
+ - **`implementation-valid`** — blocks code merge. Verifies that every `Test obligation` is materialized into ≥1 executable test with a two-way ID reference, every `approved` ID is covered by green tests, every `removed` ID has tests for its `compatibility_action`, and every internal decision the agent took (and the spec did not fix) is enumerated in the PR. **This gate is a signal, not a proof**: a test can be syntactically green yet semantically vacuous; for a major-bump `Surface`, an explicit human review of the test is required (per-ID mutation-testing kill rate is recommended, not blocking).
29
+
30
+ ---
31
+
32
+ ## 1. The atomic unit: a typed normative ID-element
33
+
34
+ 1.1. Every normative statement in the spec is materialized as an **ID-element** with a unique stable identifier.
35
+
36
+ 1.2. The ID is **semantically neutral** at the local-number level: `REQ-017`, `CONTRACT-004`, `SCN-021`. Global uniqueness is composed via the partition namespace: `<partition_id>:<neutral_id>` (e.g. `billing:REQ-017`). The partition name is neutral (`billing`, `auth`), but it is not the domain semantics inside the element; renaming meaning inside an ID is forbidden, renaming a partition is a separate procedure with reference migration.
37
+
38
+ 1.3. IDs are never reused. A removed element stays in the graph as a `removed` record; the number is not handed to a new element. Numbers are allocated through the partition's `id_namespace` — no central allocator is needed.
39
+
40
+ 1.4. Every ID-element belongs to one of the following closed **templates**. The full list of required and conditional fields is in §14.
41
+
42
+ | Template | When to use |
43
+ |----------|-------------|
44
+ | `Behavior` | Synchronous observable behavior (single-actor). Fields: `given`, `when`, `then`, `negative_cases`, `out_of_scope`, `applicability`, `Policy`-ref, `concurrency_model` (C), `data_scope` (C). |
45
+ | `Invariant` | A property that always/never holds. Fields: `always`/`never`, `scope`, `evidence` (`public_api`/`test_probe`/`db_constraint`/`operational_signal`), `stability` (`contractual`/`internal`), `data_scope` (`new_writes_only`/`all_data`/`post_migration:<MIG-ID>`), `applicability`, `concurrency_model` (C), `negative_cases`, `out_of_scope`. |
46
+ | `Contract` | A boundary contract (HTTP API, DB schema, event, file format, CLI, error code). Fields: `Surface`-ref, `schema`, `preconditions`, `postconditions`, `external_identifiers`, `compatibility_rules`, `error_taxonomy` (R on boundary), `applicability`, `concurrency_model` (C), `data_scope` (C). |
47
+ | `Scenario` | Stateful / async / event-driven scenario. Fields: `initial_state`, `trigger`, `observable_sequence`, `ordering ∈ {strict, partial, unordered}`, `idempotency`, `retry_policy`, `timeout`, `applicability`. |
48
+ | `NFR` | Non-functional requirement. Fields: `target` (SLO/resource declaration with `metric`, `threshold`, `environment`), `verification_obligation` with `verification_stage ∈ {ci_unit, ci_integration, perf_lab, staging_canary, prod_slo}` and a reference to an artefact (load profile, dashboard query, SLO recorder). `implementation-valid` accepts "green" only for the matching stage; everything else is `awaiting_evidence:<stage>`. |
49
+ | `Constraint` | An external constraint on technology / implementation. Fields: `constraint`, `rationale` (regulatory / security / compatibility). |
50
+ | `Policy` | Authorization / tenant isolation / PII redaction / audit / rate-limit policy. Fields: `policy_kind`, `applicability` (scope of ID classes), `negative_test_obligations`. Every boundary `Contract`/`Behavior` MUST reference ≥1 `Policy` or carry `Policy: not_applicable` with rationale. |
51
+ | `Migration` | A change to data at-rest or a switchover procedure. Fields: `target_ids`, `direction ∈ {forward_only, reversible}`, `mode ∈ {online, offline, dual_write, backfill, dual_emit_with_legacy_text}`, `data_window`, `success_criteria`, `rollback_plan`, `tests_pre`, `tests_during`, `tests_post`, `data_scope`, `partition_slice[]` (migration slices per partition, each with its own `approval_record`), `coordinator_id` (single ID joining the slices), `enforcement_stage` (see §11), `runtime_state ∈ {pre_cutover, in_progress, cutover_done, rolled_back}` (see §11). A cross-partition Migration **MUST NOT** be authored as a single block with joint approval — it MUST be split via `partition_slice[]` with local approval per slice plus `coordinator_id`, otherwise the migration does not fit into the iterative debt budget §6. |
52
+ | `Delta` | A change in behavior relative to `As-is`. Fields: `target_id`, `kind ∈ {preserve, replace, remove, migrate}`, `compatibility_action ∈ {reject, ignore, migrate, no_longer_guaranteed}`, `tests_old_behavior`, `tests_new_behavior`, `baseline_version`. |
53
+ | `GeneratedArtifact` | Generated code / client / SDK. Fields: `source_ids`, `generator` + `version`, `command`, `output_paths`, `regeneration_mode ∈ {clean, with_whitelisted_patches:<PATCH-ID...>}`, `published_surface ∈ {yes, no}`, `Surface`-ref (R if `published_surface=yes`). |
54
+ | `ExternalDependency` | Third-party provider (Stripe, S3, CRM, identity provider). Fields: `provider`, `provider_surface@version`, `authority_url_or_doc`, `consumer_contract` (what we send and expect, pinned to `provider_surface@version`), `drift_detection.mechanism ∈ {contract_test_against_sandbox, openapi_diff, schema_registry_subscription, changelog_watcher, none_with_review_by:<date>}`, `last_verified_at`, `auth_scope` (C), `rate_limits` (C), `retry/idempotency` (C), `error_taxonomy` (C), `sandbox_or_fixture` (C). |
55
+ | `LocalizationContract` | Localization / internationalization. Fields: `message_id` (stable external identifier), `icu_args_schema`, `locale_coverage`, `fallback_chain`, `text_is_contract ∈ {yes, no}`, `timezone/currency/source` (C), `collation_rule` (C), `rtl_layout` (C). Boundary errors reference a stable `code/message_id`, never localized text. |
56
+ | `Surface` | An external compatibility unit (HTTP API, SDK, public DB schema, event bus, CLI, published generated artifact). Fields: `name`, `version`, `boundary_type ∈ {api, sdk, event_bus, cli, public_db, public_storage, generated_published_artifact}`, `members[]` (Contract-ID, GeneratedArtifact-ID), `consumer_compat_policy`. Semver applies per-Surface. |
57
+ | `Partition` | A team / domain area of responsibility. Fields: `partition_id` (neutral), `owner_team`, `gate_scope`, `dependencies_on_other_partitions[]` (only via `Surface@version`/`Policy@version` references), `default_policy_set[]` (see §10.1), `unmodeled_budget` (see §13.6). |
58
+ | `Implementation binding` | A link between normative IDs and internal artefacts (table names, queues, jobs, storage keys). Fields: `target_ids`, `authority ∈ {code_annotation, migration_file, iac_state, schema_registry, db_introspection, manual_inventory}`, `verification_method`. Not normative for compatibility; used only where internal names are operationally unavoidable (`Migration`, test probes, data lifecycle). |
59
+ | `Open-Q` | An open question. Fields: `question`, `options[]` (≥2 with consequences), `blocking ∈ {yes, no}`, `owner`, `default_if_unresolved` (R when `blocking=no`). |
60
+ | `ASSUMPTION` | A default taken on a non-blocking `Open-Q` or an explicit assumption. Fields: `assumption`, `source_open_q` (if any), `blocking ∈ {yes, no}`, `review_by`, `default_if_unresolved`, `tests`, `partition_id`. |
61
+
62
+ 1.5. **Field types instead of "closed enum for everything".** Normative fields are typed from a closed set: `enum`, `scalar_with_unit`, `range_with_unit`, `schema_dsl`, `predicate_dsl`, `reference`, `bounded_free_text_with_review`. Every field of every template declares its field type; the linter from §12 validates the value against the declared type. Free prose is allowed only in `Context`, `Glossary`, `bounded_free_text_with_review` (with an explicit review marker), and comments.
63
+
64
+ 1.6. Every required field carries a value of the required field type or an explicit `not_applicable` with a one-line `reason`. An empty field in a normative section is invalid spec.
65
+
66
+ 1.7. An element that cannot be checked from outside is rewritten via **`Invariant.evidence`**: only `evidence: public_api` and explicitly `stability: contractual` operational signals become external contract. `test_probe`, `db_constraint`, internal `operational_signal` are allowed for verification but not for compatibility with external consumers (this does not promote logs and metrics into a public API).
67
+
68
+ ---
69
+
70
+ ## 2. Spec document structure
71
+
72
+ A partition's spec document MUST contain the following sections in this exact order:
73
+
74
+ 1. **`Context`** (non-normative). Any statement in `Context` referenced by code or tests MUST be lifted into a normative element or the `Glossary`.
75
+ 2. **`Glossary`**. The agent has no right to interpret a term that is not in `Glossary` — it MUST halt and raise `Open-Q`.
76
+ 3. **`Partition`** (partition metadata).
77
+ 4. **`Brownfield baseline`** — `Discovery scope` + as-is elements + `coverage_evidence` + `freshness_token`.
78
+ 5. **`Surfaces`** — `Surface` elements.
79
+ 6. **`Requirements`** — `Behavior` / `Invariant` / `Scenario` / `NFR`.
80
+ 7. **`Data contracts`** — `Contract` elements.
81
+ 8. **`External dependencies`** — `ExternalDependency` elements.
82
+ 9. **`Generated artifacts`** — `GeneratedArtifact` elements.
83
+ 10. **`Localization`** — `LocalizationContract` elements.
84
+ 11. **`Policies`** — `Policy` elements.
85
+ 12. **`Constraints`** — `Constraint` elements.
86
+ 13. **`Migrations`** — `Migration` elements.
87
+ 14. **`Deltas`** — `Delta` elements.
88
+ 15. **`Implementation bindings`** — `Implementation binding` elements.
89
+ 16. **`Open questions`** — `Open-Q` elements.
90
+ 17. **`Assumptions`** — `ASSUMPTION` elements.
91
+ 18. **`Out of scope`** — global exclusions.
92
+
93
+ A missing section (even an empty one with explicit `none`) makes the spec invalid.
94
+
95
+ ---
96
+
97
+ ## 3. The "spec vs agent" boundary
98
+
99
+ ### 3.1. The spec MUST fix
100
+ - externally observable system behavior;
101
+ - boundary contracts (`Surface` + `Contract`);
102
+ - domain invariants and forbidden states (`Invariant`);
103
+ - external identifiers (API field names, events, CLI, public-DB tables/columns, error codes, message_id) — part of `Surface`/`Contract`/`LocalizationContract`;
104
+ - third-party dependencies via `ExternalDependency`, never via a direct `Contract`;
105
+ - external constraints as `Constraint` with rationale;
106
+ - security, multitenancy, audit, rate-limit as `Policy` with explicit negative test obligations;
107
+ - data-at-rest migrations as `Migration`;
108
+ - conditional behavior via `applicability` (feature_flag, tenant, locale, env, plan_tier, api_version).
109
+
110
+ ### 3.2. The spec MUST NOT fix
111
+ - internal file / module / class / function / variable names (exception — `Implementation binding`, non-normative for compatibility);
112
+ - library or framework choice (unless wrapped as a `Constraint` with external rationale);
113
+ - directory layout;
114
+ - internal layering / service split, when not externally observable;
115
+ - effort estimates, schedules, owner assignments.
116
+
117
+ ### 3.3. The agent's domain
118
+ Anything not fixed in §3.1 as normative is the agent's call. **The agent MUST list, in the PR, every internal decision it took** that the spec did not fix (names, structure, libraries, internal split). That list becomes candidates for new `REQ`/`Constraint`/`Policy`/`ASSUMPTION` in the next cycle.
119
+
120
+ ---
121
+
122
+ ## 4. Traceability and Test obligations
123
+
124
+ 4.1. **Two-way N:M traceability** ID ↔ test. Before code-gen there are no executable tests, so traceability runs through **`Test obligation`**: every normative ID declares `predicate`, `test_template ∈ {unit, integration, contract, property, e2e, perf}`, `boundary_classes`, `failure_scenarios`. Any `REQ`/`Behavior`/`Scenario`/`NFR`/`Contract`/`Invariant`/`Policy` without a `Test obligation` is invalid.
125
+
126
+ 4.2. After implementation, every `Test obligation` MUST be materialized into ≥1 executable test with a two-way reference `@covers <partition>:<id>,...` or equivalent.
127
+
128
+ 4.3. Coverage is valid at `implementation-valid` only when, for every ID, the following are checked: happy path, boundary cases, declared failure scenarios, and (for boundary `Contract` with `Policy`) negative authz / tenant-isolation / redaction tests.
129
+
130
+ 4.4. **`implementation-valid` is a signal, not a proof.** A test can be green and semantically empty (`assert response is not None`). For a major-bump `Surface`, explicit human review of the test is required: oracle/assertion summary + input classes + negative oracle — this does not replace the linter, it surfaces the semantic gap the linter cannot close.
131
+
132
+ ---
133
+
134
+ ## 5. Ambiguities, open questions, defaults
135
+
136
+ 5.1. **Weasel words are banned** in normative sections (`Requirements`, `Data contracts`, `Constraints`, `Deltas`, `Invariants`, `Migrations`, `Policies`, `External dependencies`, `Localization`). The canonical word list lives at `skills/spec-driven-development/data/weasel-words.json` with two classes:
137
+
138
+ - `absolute` — blocked anywhere inside a normative section regardless of which template field they appear in. Examples: `as a rule`, `etc.`, `and so on`, `should usually`, `similar to`, `approximately`, `best-effort`, `informally`, plus Russian `возможно/вероятно/обычно`.
139
+ - `modal_in_normative` — modal phrases `may be`/`might be`. Blocked only inside template fields marked `is_normative=true` (e.g. `Behavior.then`, `Invariant.always`, `Contract.schema`). Allowed in `notes`/`description`/`Context`/`Glossary`/comments and in `Scenario`-prose where the alternative is captured by an explicit `options[]`/branching trigger. Same rationale as the `absolute` class — modality without a closed enum is non-binding — but the rule needs field-level scoping to avoid a false-positive flood in brownfield baselines with long descriptive sections.
140
+
141
+ Both rule and `agent-sdd` import the JSON list at build time; the CLI's `tests/unit/weasel-words-sync.test.ts` asserts byte-equivalence with this canonical source. Expanding the list (adding more absolute words, or singletons `may`/`might`, or other phrases from earlier drafts of this rule like `as needed`/`where appropriate`) requires a coordinated PR in both repos plus a minor bump on `Surface: diagnostics` (SUR-009 in `sdd-cli/spec/spec.md`). Mechanical enforcement: ENF-001 in `rules/enforcement_registry.md`. Drift detection between rule and CLI: ENF-022 (`sdd:weasel-source-drift`, planned).
142
+
143
+ 5.2. Any detected ambiguity is raised as `Open-Q`. **The agent has no right to silently pick a variant.** With `blocking=yes` the agent halts. With `blocking=no` the agent uses `default_if_unresolved` and materializes it into an `ASSUMPTION` with `review_by` and a trace to tests, flagging in the PR as "decision taken by default, requires confirmation".
144
+
145
+ 5.3. **`ASSUMPTION` is split into `blocking` and `advisory`.** A `blocking` ASSUMPTION fails CI when its target ID is touched. An `advisory` ASSUMPTION past `review_by` produces a warning and an automatic `Open-Q: review_overdue:<ASM-ID>`, but CI block kicks in only when the per-partition overdue-advisory budget is exceeded — otherwise the calendar becomes the main source of false positives in `spec-valid`.
146
+
147
+ 5.4. **Downgrading an ASSUMPTION `blocking → advisory` requires an `approval_record` with `approver_identity ≠ agent_identity`** (the same rule as Surface approval §7.5). Otherwise the expiry of `review_by`, or an explicit downgrade, becomes a self-approval back-door: the code-gen agent unblocks its own ASSUMPTION by simply changing the status, bypassing no-self-approval. Date expiry alone does NOT downgrade `blocking` — only an explicit downgrade with approval from a different identity.
148
+
149
+ ---
150
+
151
+ ## 6. Brownfield: Discovery scope, As-is, Delta, debt budget
152
+
153
+ 6.1. **`Discovery scope`** — a mandatory part of every partition's `Brownfield baseline`. Declares which entrypoints, datasets, flags, tenants, environments, generated artifacts have been surveyed. Every scope item carries `coverage_evidence` (a reference to an artefact: entrypoint registry, code-skeleton dump, IaC inventory, schema registry snapshot, DB introspection).
154
+
155
+ 6.2. Anything **outside** `Discovery scope` is marked `unmodeled`. **`unmodeled` MUST NOT be silently deleted, rewritten, or declared non-existent** — modifying it requires the recon agent to expand `Discovery scope` and an explicit `Open-Q`/`Delta`. This closes the "didn't find = doesn't exist" loophole.
156
+
157
+ 6.3. **`Brownfield baseline` is non-normative on its own.** As-is becomes normative only when an explicit `Behavior`/`Invariant`/`Contract` references it as preserved. As-is does not describe the target state and does not opine on code quality.
158
+
159
+ 6.4. **Every change is authored as a `Delta`** with all required fields. Without a `Delta` the agent has no right to change existing behavior. A code↔spec contradiction without a `Delta` → agent halts + `Open-Q`.
160
+
161
+ 6.5. **`freshness_token`** (hash of recon input sources) is part of the baseline. When it diverges from current source state the baseline becomes `stale`, and `baseline-valid` blocks new `Delta`/`Migration` from reaching `implementation-valid` until the relevant scope is re-checked. Authoring of new `Delta`/`Open-Q` against a pinned `baseline_version` is still allowed (see §0).
162
+
163
+ 6.6. **Iterative debt budget.** Bringing as-is to target in one PR is impossible on a typical brownfield. Each partition has an explicit `unmodeled` / legitimate-debt budget (numeric or percent), which **shrinks per PR/sprint**; the metric is mandatory and tracked. This makes "behavior without an approved ID is removed or lifted" an iterative rule, not an "all in one go" rule.
164
+
165
+ ---
166
+
167
+ ## 7. Per-Surface versioning
168
+
169
+ 7.1. **`Surface` is the unit of semver.** Every `Contract` / `GeneratedArtifact.published_surface=yes` MUST belong to exactly one `Surface`. `Constraint`/`Open-Q`/`ASSUMPTION` have no semver (`not_applicable: no_external_surface`).
170
+
171
+ 7.2. Every normative ID-element (Surface, Contract, Behavior, Invariant, Scenario, NFR, Policy, Migration, GeneratedArtifact, ExternalDependency, LocalizationContract, Constraint) carries a unified `lifecycle.status ∈ {draft, proposed, approved, deprecated, removed}` and `version: N`. Lifecycle is shared across all normative templates, not Surface only.
172
+
173
+ 7.3. Agent rights by status:
174
+ - `draft` — experimental generation **only in the sandbox** (`spike/`). Forbidden to touch the `approved` graph.
175
+ - `proposed` — spec-valid for review, but **not `implementation-valid` and not mergeable**. This is the legitimate state of a change-set between recon and approval; it removes the livelock of "the change exists, but `draft` is forbidden and `approved` requires human approval".
176
+ - `approved` — required to be implemented.
177
+ - `deprecated` — requires `sunset_version` (= the version of the `Surface` the ID belongs to) and `replacement_id`.
178
+ - `removed` — requires `compatibility_action`. Removing behavior from the spec does **NOT** automatically mean removing the code: the agent acts strictly per `compatibility_action`.
179
+
180
+ 7.3-bis. **Approval ordering by reference graph.** Approval of a normative ID requires every referenced normative ID (Surface→Contract→Policy/ExternalDependency, Migration→Invariant, Behavior→Policy, etc.) to be `≥ approved` in the same PR or earlier. An `approved` Surface with a `proposed` Policy/Contract is forbidden — otherwise `Policy-ref` and other references degrade into dangling pointers and open a new class of gate bypass.
181
+
182
+ 7.4. **The diff between revisions** is a machine-readable list `(id, old_status → new_status, semver_change)`. Per-`Surface` `semver_change` is computed as `max` over the affected IDs of that Surface:
183
+ - `major` — contract break; requires `Delta` + breakage tests + human review of the test + (for a published Surface) a compatibility plan.
184
+ - `minor` — extension; existing tests stay green.
185
+ - `patch` — wording refinement without changing the acceptance predicate.
186
+
187
+ 7.4-bis. **Bump cascading via references.** A bump to a `Policy` or an `Invariant(stability=contractual)` referenced by a `Surface` MUST cascade into the `semver_change` of every referencing Surface:
188
+ - any change to Policy/Invariant content ⇒ `≥ minor` on every referencing Surface;
189
+ - a change to the **predicate** of a Policy (authz rule, tenant scope, redaction rule, rate-limit threshold) or the **predicate** of an Invariant ⇒ `major` on every referencing Surface.
190
+
191
+ Without this rule, a breaking change in a centralized Policy would land as `minor` in every consumer, bypassing the human-review requirement for major bumps.
192
+
193
+ 7.5. **`approval_record`** is required for every `approved`/`deprecated`/`removed` ID: `owner_role`, `approver_identity`, `timestamp`, change-request reference, `scope`. **Self-approval by the code-gen agent is forbidden** (a hard rule of `spec-valid`). A major-bump `Surface` requires an owner of the same or higher role. `spec-valid` distinguishes the first-time approval of a new ID from the approval of a diff to an existing approved ID.
194
+
195
+ 7.5-bis. **Two-step approval ceremony.** Approval is split into two atomic operations to preserve the invariant «`lifecycle.status=approved` ⇒ reference graph is consistent».
196
+
197
+ - **Step 1 — *attestation*.** The owner records a `pending_approval_record` (owner_role, approver_identity, timestamp, change_request, scope) in a plan-namespace artefact, never directly on a `proposed` ID. No `lifecycle.status` changes. Plan namespace is auditable independently of spec files.
198
+ - **Step 2 — *finalization*.** The plan is validated against the reference graph: every Surface member, Policy-ref, Migration target must already be `≥approved` or part of the same plan. On success, the `lifecycle.status` of every ID in the plan flips from `proposed` to `approved` in one atomic write, and the attestation is copied into the ID-level `approval_record`. On failure, nothing is written; the plan stays open until references resolve.
199
+
200
+ Self-approval ban applies to step 1 (the attestation must come from a non-agent identity). Step 2 is purely mechanical — it writes nothing of its own substance, only what step 1 attested.
201
+
202
+ This separation removes the previous race between `approve` and `ready`: there is no transient state where a Surface is `approved` while a referenced Contract is still `proposed`. Mechanical enforcement: ENF-002 in `rules/enforcement_registry.md` — split into ENF-002A (executor `sdd ready`, diagnostic `surface_unapproved_ref`) and ENF-002B (executor `sdd finalize`, envelope reason `proposed-references`).
203
+
204
+ ---
205
+
206
+ ## 8. External identifiers as contract
207
+
208
+ 8.1. Names of API fields, events, CLI commands, public-DB tables/columns, error codes, HTTP headers, queues, file formats, localization message_ids — are **part of the contract** and live inside `Contract`/`Surface`/`LocalizationContract`.
209
+
210
+ 8.2. Evolution goes through explicit rules: `alias`, `deprecation` (with `sunset_version`), `migration` (with procedure and test).
211
+
212
+ 8.3. Silent renaming of an external identifier is forbidden, regardless of how "safe" it looks.
213
+
214
+ 8.4. For text that became a de-facto contract in legacy code (downstream parsers rely on it), the transition to `code/message_id`-as-contract is authored via `Migration.mode: dual_emit_with_legacy_text` with a fixed window before the old text is removed.
215
+
216
+ ---
217
+
218
+ ## 9. Predicate testability and concurrency
219
+
220
+ 9.1. The predicate of a `Behavior`/`Contract` is expressed via observable inputs/outputs/state. An `Invariant` uses the `evidence` channel (see §1.7).
221
+
222
+ 9.2. **`concurrency_model`** is required for `Behavior`/`Contract`/`Invariant` that cross a concurrency boundary:
223
+ - `actor_concurrency ∈ {single, multi_per_resource, multi_global}`;
224
+ - `read_consistency ∈ {strong, read_your_writes, monotonic, eventual:<max_lag>}`;
225
+ - `idempotency ∈ {none, at_least_once_with_key:<field>, exactly_once_with_key:<field>}`;
226
+ - `time_source ∈ {none, monotonic, wall_clock:<max_skew>, external:<service>}`.
227
+
228
+ Negative `Test obligations` are required for: race window, replay, clock skew, partition. Without an explicit `concurrency_model`, a boundary element is considered ambiguous.
229
+
230
+ 9.3. `Scenario` is an observable trace with explicit `ordering` and `timeout`.
231
+
232
+ 9.4. `NFR` carries `verification_obligation.verification_stage`. `implementation-valid` accepts green only for the matching stage; everything else is `awaiting_evidence:<stage>`. An NFR without `measurement_method` or without an artefact reference is invalid.
233
+
234
+ ---
235
+
236
+ ## 10. Security, policies, multitenancy, localization
237
+
238
+ 10.1. **`Policy`** is a separate first-class template for authorization, tenant isolation, PII redaction, audit, rate-limit. Every boundary `Contract`/`Behavior` MUST reference ≥1 `Policy`. To avoid an avalanche of `Policy: not_applicable+rationale` in brownfield partitions with dozens of legacy endpoints, **every `Partition` declares a `default_policy_set`** (the minimal set of Policy IDs applied by default to every boundary Contract/Behavior of the partition). A specific Contract either inherits `default_policy_set` implicitly, or **explicitly opts out via a `Policy.override` block** that names the rejected Policy ID and a rationale; a bare `not_applicable` without an override block is invalid. This turns mass absence of Policy from a silent ritual into an active decision the linter sees and counts.
239
+
240
+ 10.2. Negative `Test obligations` are required for every `Policy`: unauthorized actor, cross-tenant access, forbidden data exposure, rate-limit violation.
241
+
242
+ 10.3. **`LocalizationContract`** is a separate first-class template. Boundary errors reference a stable `code/message_id`, never localized text. `text_is_contract: yes` lifts the text to contract level (with all alias/deprecation/migration rules); the default is `no`.
243
+
244
+ 10.4. **Conditional behavior via `applicability`** (feature_flag, tenant, locale, env, plan_tier, api_version). A missing axis = "invariant across all values" requires an explicit `Open-Q` the first time a variable on that axis is detected in the code.
245
+
246
+ ---
247
+
248
+ ## 11. Third-party APIs, migrations, generated code
249
+
250
+ 11.1. **`ExternalDependency`** is a separate template for third-party providers. Recording provider-owned behavior as a regular `Contract` without provenance is forbidden — otherwise the agent freezes incidentally observed Stripe/S3/CRM behavior as if it were ours. `consumer_contract` is always pinned to `provider_surface@version`.
251
+
252
+ 11.2. **`drift_detection.mechanism`** is a closed set: `contract_test_against_sandbox`, `openapi_diff`, `schema_registry_subscription`, `changelog_watcher`, `none_with_review_by:<date>`. A free-text label like "watcher will be set up" is invalid.
253
+
254
+ 11.3. **`Migration`** is a separate first-class template for data at-rest and switchover procedures. Without `Migration`, an `Invariant`/`Contract` is ambiguous with respect to legacy data: a mandatory `data_scope ∈ {new_writes_only, all_data, post_migration:<MIG-ID>}` is required.
255
+
256
+ 11.3-bis. **Migration spec-lifecycle vs runtime-state — two separate axes**, and conflating them breaks the gate.
257
+ - **Spec-lifecycle** (governance, see §7.2): `lifecycle.status ∈ {draft, proposed, approved, deprecated, removed}` — describes whether the migration is agreed upon as a normative document.
258
+ - **Runtime-state** (operational evidence): `runtime_state ∈ {pre_cutover, in_progress, cutover_done, rolled_back}` — describes **where the migration's execution currently is** in the repo.
259
+
260
+ An `approved` Migration in `pre_cutover` runtime-state is normal; treating it as "executed" merely because it is approved would be a false-green `implementation-valid`.
261
+
262
+ 11.3-ter. **`enforcement_stage` for normative IDs tied to a Migration.** Every `Invariant`/`Contract`/`Behavior` with `data_scope=post_migration:<MIG-ID>` MUST carry `enforcement_stage` that points to a **deterministic test-controllable marker in the repo** (`feature_flag`, `migration completion sentinel`, `cutover_marker:<MIG-ID>` — but NOT a live deployment state, NOT a production signal, NOT a runtime feature-flag service). CI runs pre/during/post-cutover test sets by toggling that marker, **never by reading the environment** — otherwise the gate starts depending on deployment state and loses reproducibility.
263
+
264
+ 11.3-quater. **`implementation-valid` checks only the tests applicable to the current Migration runtime-state**:
265
+ - `runtime_state = pre_cutover` → `tests_pre` green; `tests_post` may be `awaiting_marker:<MIG-ID>:cutover_done` and do not block merge;
266
+ - `runtime_state = in_progress` → `tests_pre` + `tests_during` green (for `dual_write`/`backfill`/`dual_emit_with_legacy_text`);
267
+ - `runtime_state = cutover_done` → `tests_post` green; dependent `Invariant`s with `enforcement_stage=post_migration:<MIG-ID>` MUST be green;
268
+ - `runtime_state = rolled_back` → `tests_pre` green; `Invariant`s with `data_scope=post_migration:<MIG-ID>` move to `awaiting_marker` or `deprecated`.
269
+
270
+ This closes the `Migration → Invariant → implementation-valid` cycle: the invariant becomes mandatory only after cutover, evidenced by a deterministic marker, not before.
271
+
272
+ 11.4. **`GeneratedArtifact`** — normative IDs are allowed only on the source schema/contract. `regeneration_mode ∈ {clean, with_whitelisted_patches:<PATCH-ID...>}` — every patch gets its own normative ID and `Test obligation`. `published_surface: yes` makes the generated output its own `Surface` with independent semver.
273
+
274
+ 11.4-bis. **Derived semver for `GeneratedArtifact(published_surface=yes)`.** The `semver_change` of the generated artifact's own Surface is computed as:
275
+ ```
276
+ semver_change(generated_surface) = max(
277
+ semver_change(upstream_source),
278
+ structural_diff_class(generated_emission)
279
+ )
280
+ ```
281
+ where `structural_diff_class` evaluates a change in **the emission itself** (field order in serialization, renaming of generated identifiers, wire-format change, removal/renaming of generated methods). **A structural-breaking diff is mandatorily `major`, regardless of the upstream bump** — a minor in the source plus a breaking diff in the emission (field order, names in serialization) yields a `major` on the generated Surface, otherwise the SDK breaks downstream consumers under the cover of a minor.
282
+
283
+ ---
284
+
285
+ ## 12. Storage format and linter
286
+
287
+ 12.1. The spec is stored in a format amenable to **static checking**: structured markdown with YAML frontmatter on ID-elements, or standalone YAML/JSON, or markdown with fixed headers and fields. Every field of every template declares its field type from §1.5.
288
+
289
+ 12.2. **The spec linter runs in CI as a blocking step.** The linter implements:
290
+ - the section structure of §2 per partition;
291
+ - the `Template Requiredness Matrix` §14 — presence of required and conditional (by trigger) fields;
292
+ - field types §1.5;
293
+ - the weasel-word ban §5.1 in normative sections;
294
+ - two-way ID ↔ `Test obligation` traceability;
295
+ - per-`Surface` semver markup of the diff §7.4;
296
+ - `approval_record` presence and the self-approval ban §7.5;
297
+ - `baseline-valid` per partition §0.
298
+
299
+ 12.3. Every normative requirement has exactly one row in `rules/enforcement_registry.md` with an explicit `enforcement_class`. Process-class requirements (`agent-halt`, `agent-judgment`, `human-review`) are first-class: their `process_owner` and `review_trigger` are mandatory and they have the same normative force as mechanical-class requirements. The registry distinguishes *what the linter cannot mechanically prove* (process-only, but still nominative) from *what the methodology has not bothered to formalise* — only the second category is a wish.
300
+
301
+ ---
302
+
303
+ ## 13. Partitioning
304
+
305
+ 13.1. **`Partition`** is a first-class object. Fields: `partition_id` (neutral), `owner_team`, `gate_scope`, `dependencies_on_other_partitions[]`, `default_policy_set[]` (the minimal set of Policy IDs applied by default to every boundary Contract/Behavior of the partition — see §10.1), `unmodeled_budget` (iterative debt-budget metric — see §13.6).
306
+
307
+ 13.2. Cross-partition references go only through `Surface@version` or `Policy@version`. Direct references to another partition's internal IDs are forbidden.
308
+
309
+ 13.3. ID allocation is local to the partition (`<partition_id>:<neutral_id>`); this fulfils §1.3 without a central allocator and removes merge conflicts on ID allocation.
310
+
311
+ 13.4. `gate_scope` defines exactly what each gate (`baseline-valid`/`spec-valid`/`implementation-valid`) checks for this partition and its declared dependencies, not for the whole repository.
312
+
313
+ 13.5. A cross-partition `Migration` is authored via `Migration.partition_slice[]` (each slice belongs to a single partition and carries a local `approval_record` from that partition's owner team) plus a single `coordinator_id`. Joint approval as a monolith is forbidden — it does not fit into the iterative debt budget §6 and would block the migration indefinitely.
314
+
315
+ 13.6. **`unmodeled_budget`** materialises the iterative debt budget §6.6 as a typed, mechanically-checkable Partition field. Required sub-fields: `current` (int ≥0; current `unmodeled` count or weighted score), `baseline_at` (ISO date when `baseline_value` was recorded), `baseline_value` (int ≥0; reference value the budget is shrinking from), `trend ∈ {monotonic_non_increasing, monotonic_decreasing}`. Mechanical: `sdd lint` checks form (ENF-020 `debt_budget_increased`); `sdd ready` with `--against <ref>` checks monotonic constraint vs the recorded baseline. Human: partition owner reviews semantic content of debt at sprint close — the mechanical check guarantees the metric is moving, not that the underlying debt is meaningfully reduced.
316
+
317
+ ---
318
+
319
+ ## 14. Template Requiredness Matrix
320
+
321
+ `R` = required (always); `C` = conditional (R when triggered); `O` = optional. For conditional fields a default is allowed, but when the trigger fires an explicit value is required.
322
+
323
+ | Template | Field | Card. | Conditional trigger / default |
324
+ |---|---|---|---|
325
+ | `Behavior` | `applicability` | R | default `invariant_to_all_axes`; explicit value R when an axis variable is detectable in scope |
326
+ | `Behavior` | `concurrency_model` | C | R if Behavior crosses a Surface boundary or `actor_concurrency=multi` is detected in baseline |
327
+ | `Behavior` | `data_scope` | C | R if Behavior interacts with persistent state or there is an active Migration on the affected data |
328
+ | `Behavior` | `Policy`-ref | C | R on a boundary; otherwise `Policy: not_applicable` with rationale |
329
+ | `Contract` | `Surface`-ref | R | always |
330
+ | `Contract` | `applicability` | R | default allowed |
331
+ | `Contract` | `error_taxonomy` | C | R if `Surface.boundary_type ∈ {api, sdk, event_bus, cli, public_db, public_storage}` |
332
+ | `Contract` | `concurrency_model` | C | R if Surface is concurrent; default `single_actor_per_resource` for sync request/response |
333
+ | `Contract` | `data_scope` | C | R if Contract reads/writes persisted data |
334
+ | `Invariant` | `evidence` + `stability` | R | always |
335
+ | `Invariant` | `data_scope` | R | default `all_data`; explicit value R when there is a Migration or data-shape evolution |
336
+ | `Invariant` | `applicability` | R | default allowed |
337
+ | `Invariant` | `concurrency_model` | C | R if invariant references shared mutable state or contains a temporal/ordering predicate |
338
+ | `Scenario` | all template fields | R | always |
339
+ | `NFR` | `target` | R | always |
340
+ | `NFR` | `verification_obligation.stage` + artefact ref | R | always |
341
+ | `Migration` | all template fields (`target_ids`, `direction`, `mode`, `data_window`, `success_criteria`, `rollback_plan`, `tests_pre`, `tests_during`, `tests_post`, `data_scope`, `runtime_state`, `enforcement_stage`) | R | always (the template is created only for migrations) |
342
+ | `Migration` | `partition_slice[]`, `coordinator_id` | C | R if migration crosses >1 partition; joint approval as a monolith is forbidden |
343
+ | `Delta` | `baseline_version` | R | always (Delta is pinned to a specific baseline revision) |
344
+ | Normative ID tied to a Migration | `enforcement_stage` (points to a deterministic test-controllable marker in the repo) | C | R when `data_scope=post_migration:<MIG-ID>` |
345
+ | `Partition` | `default_policy_set[]` | R | always (even if empty — explicitly `[]`) |
346
+ | `Partition` | `unmodeled_budget` | R | always; sub-fields `current`, `baseline_at`, `baseline_value`, `trend ∈ {monotonic_non_increasing, monotonic_decreasing}` (see §13.6) |
347
+ | `Contract`/`Behavior` boundary | `Policy.override` block when opting out of `default_policy_set` | C | R on opt-out; bare `not_applicable` without an override block is invalid |
348
+ | `Policy` | all template fields | R | always |
349
+ | `Constraint` | `constraint`, `rationale` | R | always |
350
+ | `GeneratedArtifact` | `source_ids`, `regeneration_mode`, `published_surface` | R | always |
351
+ | `GeneratedArtifact` | `Surface`-ref | C | R if `published_surface=yes` |
352
+ | `ExternalDependency` | `provider`, `provider_surface@version`, `drift_detection.mechanism`, `last_verified_at`, `consumer_contract` | R | always |
353
+ | `ExternalDependency` | `auth_scope` | C | R if integration requires auth |
354
+ | `ExternalDependency` | `rate_limits` | C | R if provider publishes or enforces rate limits |
355
+ | `ExternalDependency` | `retry/idempotency` | C | R if interaction is not read-only-fetch |
356
+ | `ExternalDependency` | `error_taxonomy` | C | R if provider errors are observable on our boundary |
357
+ | `ExternalDependency` | `sandbox_or_fixture` | C | R if `drift_detection.mechanism = contract_test_against_sandbox` |
358
+ | `LocalizationContract` | `message_id`, `icu_args_schema`, `locale_coverage`, `fallback_chain`, `text_is_contract` | R | always inside the template |
359
+ | `LocalizationContract` | `timezone/currency/source` | C | R if behavior depends on time/currency formatting |
360
+ | `LocalizationContract` | `collation_rule` | C | R if behavior contains string sorting/comparison |
361
+ | `LocalizationContract` | `rtl_layout` | C | R if `locale_coverage` includes RTL locales |
362
+ | `Surface` | `name`, `version`, `boundary_type`, `members[]`, `consumer_compat_policy` | R | always |
363
+ | `Partition` | `partition_id`, `owner_team`, `gate_scope`, `dependencies_on_other_partitions` | R | always |
364
+ | `Implementation binding` | `target_ids`, `authority`, `verification_method` | R | always (the template is created only when binding is needed) |
365
+ | ID-level | `partition_id` namespace + neutral local ID | R | always |
366
+ | ID-level (normative templates) | `lifecycle.status ∈ {draft, proposed, approved, deprecated, removed}` | R | always; unified lifecycle for Surface/Contract/Behavior/Invariant/Scenario/NFR/Policy/Migration/GeneratedArtifact/ExternalDependency/LocalizationContract/Constraint |
367
+ | ID-level | `approval_record` (owner_role, approver_identity, timestamp, change_request, scope) | C | R for `approved`/`deprecated`/`removed`; forbidden for `draft`/`proposed`; self-approval ban is a hard rule of `spec-valid`; Surface approval requires `≥approved` for every referenced normative ID |
368
+ | `ASSUMPTION` downgrade `blocking → advisory` | `approval_record` with `approver_identity ≠ agent_identity` | R | always on downgrade; expiry of `review_by` alone does NOT downgrade `blocking` |
369
+ | `Open-Q` | `question`, `options[]` (≥2), `blocking`, `owner` | R | always |
370
+ | `Open-Q` | `default_if_unresolved` | C | R when `blocking=no` |
371
+ | `ASSUMPTION` | `assumption`, `blocking ∈ {yes, no}`, `review_by`, `default_if_unresolved`, `tests`, `partition_id` | R | always |
372
+ | `ASSUMPTION` | `source_open_q` | O | when the assumption flows from an explicit Open-Q |
373
+ | Discovery / baseline | `Discovery scope`, `coverage_evidence`, `freshness_token` | R | per partition |
374
+
375
+ **Process rules (not template fields):**
376
+ - Iterative debt budget for as-is → target — R.
377
+ - `baseline-valid` as the third gate, partition-scoped — R.
378
+ - `ASSUMPTION advisory` with overdue budget per partition — R.
379
+ - Human review of the test on a major-bump `Surface` — R.
380
+ - Per-ID mutation-testing kill rate — O.
381
+ - Field types §1.5 — R, every template declares the type of each of its fields.
382
+
383
+ ---
384
+
385
+ ## 15. Agent behavior on existing code
386
+
387
+ 15.1. **Recon first** (read-only recon agent). Builds `Discovery scope` + `Brownfield baseline` without interpretation and without opinion on code quality. This is a separate artefact. `coverage_evidence` is mandatory. No `Delta`/`Migration` until `baseline-valid`.
388
+
389
+ 15.2. **Then target.** The agent authors `Requirements`/`Data contracts`/`Policies`/`Constraints`/`Deltas`/`Migrations` against the `Brownfield baseline`. Every as-is fact the agent decides to preserve becomes a normative element. Every difference from as-is is authored as a `Delta`. Every data-at-rest migration is a `Migration`.
390
+
391
+ 15.3. **No silent decisions.** Stop conditions split into two enforcement classes — `agent-halt` (reflex on a closed condition) and `agent-judgment` (semantic classification). Each item is tagged accordingly; the split is normative for the PR report shape (§15.4). Full mapping: `rules/enforcement_registry.md` rows ENF-005, ENF-006, ENF-023..ENF-034. The agent MUST halt and raise `Open-Q` on any of:
392
+ - `[agent-halt]` a term not in `Glossary`;
393
+ - `[agent-halt]` behavior outside `Discovery scope` (`unmodeled`);
394
+ - `[agent-halt]` a code↔spec contradiction without a `Delta`;
395
+ - `[agent-halt]` a weasel word in a normative section;
396
+ - `[agent-halt]` removal without a `compatibility_action`;
397
+ - `[agent-halt]` missing `Policy`-ref on a boundary;
398
+ - `[agent-halt]` missing `applicability` when an axis variable is detectable;
399
+ - `[agent-halt]` missing `concurrency_model` on a boundary;
400
+ - `[agent-halt]` missing `data_scope` on persistent state;
401
+ - `[agent-judgment]` provider-owned behavior without an `ExternalDependency`;
402
+ - `[agent-judgment]` generated output without a `GeneratedArtifact`;
403
+ - `[agent-judgment]` text-as-contract without a `LocalizationContract`;
404
+ - `[agent-halt]` missing `baseline_version` on a `Delta`/`Migration`;
405
+ - `[agent-halt]` a `proposed → approved` Surface transition while a referenced Policy/Contract is not yet approved;
406
+ - `[agent-halt]` a Migration `runtime_state` change without updating dependent `Invariant.enforcement_stage`;
407
+ - `[agent-halt]` a structural-breaking diff in a `GeneratedArtifact(published_surface=yes)` without a major bump on its own Surface.
408
+
409
+ `agent-halt` items are reflexes on closed structural triggers — the agent recognises the violation and immediately raises `Open-Q` rather than guessing. `agent-judgment` items require semantic classification of an observed behavior — the agent decides what kind of contract the behavior is and surfaces that classification with rationale. In `sdd report --pr-summary`, `agent-halt` produces a list of raised `Open-Q`s with trigger references; `agent-judgment` produces a list of classification decisions with rationale.
410
+
411
+ 15.4. **PR report.** When generating or editing code, the agent's PR explicitly lists:
412
+ - which `Test obligation`s were closed and by which tests (with oracle/assertion summary, input classes, negative oracle for major-bump);
413
+ - internal decisions taken (names, structure, libraries);
414
+ - `ASSUMPTION`s used and their `review_by`;
415
+ - remaining `Open-Q`s and why they do not block this specific PR;
416
+ - reduction of the `unmodeled` / legitimate-debt budget vs the previous PR (when applicable).
417
+
418
+ 15.5. **No code without an `approved` ID** in the long run. Behavior not covered by an `approved` element is either removed from code or lifted into the spec as `REQ`/`Contract` with resolution. This rule applies **iteratively via the debt budget §6.6**, not "all in one PR" — otherwise brownfield is paralyzed.
419
+
420
+ 15.6. **Self-approval is forbidden** in all its forms. The code-gen agent has no right to set `approval_record` on IDs it created or modified itself, **and no right to downgrade `ASSUMPTION` `blocking → advisory`** (§5.4) without a different approver_identity. Approval is a separate role (a human owner or another autonomous approver-agent with a different identity).
421
+
422
+ ---
423
+
424
+ ## 16. v3 recommendation (not a blocker)
425
+
426
+ In v2.1 the temporal axes are scattered across multiple sections and interact in subtle ways:
427
+
428
+ - baseline livelock and `freshness_token` (§0/§6) — snapshot versioning;
429
+ - spec-lifecycle `{draft, proposed, approved, deprecated, removed}` (§7.2) — governance;
430
+ - Migration runtime-state `{pre_cutover, in_progress, cutover_done, rolled_back}` (§11.3-bis) — operational evidence;
431
+ - `enforcement_stage` of normative IDs (§11.3-ter) — when an invariant becomes mandatory;
432
+ - `ASSUMPTION.review_by` (§5) — calendar deadline;
433
+ - semver chaining via references (§7.4-bis) — derived versioning.
434
+
435
+ Every cycle the second review caught walks through this layer. **In v3 the recommendation is to consolidate the temporal layer into a dedicated section `§LX "Lifecycle & temporal evidence"` with an explicit ER graph**: which axis blocks which other, which inter-axis relations are admissible, which transitions are atomic. Without this consolidation, every next patch risks opening a new temporal cycle. This is a recommendation, not a blocker, for handing v2.1 off to the agent.
436
+
437
+ ---
438
+
439
+ ## Appendix B — How to use `sdd-cli`
440
+
441
+ This appendix is the operational manual for invoking `agent-sdd`
442
+ during SDD workflow. The normative phase-to-command mapping lives in
443
+ `rules/sdd-cli-usage.md`; this appendix is the longer-form guide with
444
+ typical scenarios and rationale.
445
+
446
+ ### B.1 Bootstrapping a new SDD baseline
447
+
448
+ ```sh
449
+ # 1. Add .sdd/config.json (path to spec, baseline_id, discovery_scope).
450
+ # 2. Add an empty BrownfieldBaseline block in spec.md with placeholder token.
451
+ sdd token --format=json
452
+ # 3. Paste TOKEN and SHA into BL-001.
453
+ # 4. Approve the baseline (human identity required):
454
+ sdd approve --id "<partition>:BL-001" \
455
+ --approver alice --owner-role partition_owner \
456
+ --change-request <URL>
457
+ sdd finalize
458
+ sdd check # exit 0 confirms baseline is consistent
459
+ ```
460
+
461
+ ### B.2 Daily spec edit cycle
462
+
463
+ ```sh
464
+ # After every change to a YAML block in spec.md:
465
+ sdd lint
466
+ # Exit 0 — proceed. Exit 1 — fix diagnostics; never proceed with violations.
467
+ ```
468
+
469
+ ### B.3 Promoting a proposed ID to approved
470
+
471
+ ```sh
472
+ sdd approve --id "<part>:BEH-014" \
473
+ --approver alice --owner-role tech-lead \
474
+ --change-request <PR-URL>
475
+ # Attestation written to .sdd/plans/<uuid>.yaml. spec.md untouched.
476
+ sdd plan show
477
+ # Verify the attestation looks correct.
478
+ sdd finalize
479
+ # If exit 0: spec.md updated atomically, lifecycle.status flipped, approval_record materialised.
480
+ # If exit 1 with reason="proposed-references": some referenced IDs are still proposed.
481
+ # Either approve them in the same plan, or promote them first.
482
+ ```
483
+
484
+ ### B.4 Pre-commit gate
485
+
486
+ ```sh
487
+ sdd ready
488
+ # Exit 0 — safe to commit. Exit 1 — see violations[]:
489
+ # [unapproved] — proposed ID outside sandbox_paths
490
+ # [uncovered] — approved ID without @covers marker
491
+ # [surface_unapproved_ref] — Surface points at a non-approved member
492
+ # [surface_semver_cascade] — Policy/Invariant content change requires Surface bump
493
+ # [generated_artifact_structural_diff_unbumped] — GA emission breaking diff w/o major
494
+ # [debt_budget_increased] — Partition.unmodeled_budget grew vs --against ref
495
+ # [aggregated_lint] — upstream sdd lint violation
496
+ # [aggregated_check] — baseline-stale or baseline-dirty
497
+ ```
498
+
499
+ ### B.5 Drift response
500
+
501
+ When `sdd check` reports `baseline-stale`:
502
+
503
+ ```sh
504
+ sdd refresh > /tmp/stubs.yaml
505
+ # Each changed path becomes either a Delta stub (inside an IMP footprint) or
506
+ # Open-Q stub (in scope but no IMP claims it).
507
+ # Fill TODO fields, paste into spec.md, commit.
508
+ sdd token --format=json | jq -r .token # paste into BL.freshness_token
509
+ sdd token --format=json | jq -r .commit_sha # paste into BL.baseline_commit_sha
510
+ sdd check # exit 0
511
+ ```
512
+
513
+ ### B.6 Cross-version drift detection
514
+
515
+ ```sh
516
+ sdd doctor --rule-version --rules rules/enforcement_registry.md
517
+ # Exit 0 — CLI version, declared compatible range, and registry diagnostic
518
+ # coverage all align.
519
+ # Exit 1 with drift[]:
520
+ # [version_mismatch] — package.json version outside compatible_sdd_cli range
521
+ # [missing_diagnostic] — registry declares rule X with maturity=implemented;
522
+ # CLI does not publish X. Fix: bump CLI or downgrade maturity.
523
+ # [stale_diagnostic] — CLI publishes rule Y; registry does not know.
524
+ # Fix: add row to enforcement_registry.md.
525
+ ```
526
+
527
+ ### B.7 PR description generation
528
+
529
+ ```sh
530
+ sdd report --pr-summary --against <base-ref>
531
+ # Emits markdown with 5 sections:
532
+ # - Closed Test obligations (mechanical: list of tests with @covers markers)
533
+ # - Internal decisions (placeholder — agent fills in manually)
534
+ # - ASSUMPTIONs touched
535
+ # - Open-Q residuals
536
+ # - Debt budget delta
537
+ # Paste into PR description; expand «Internal decisions» by hand.
538
+ ```
539
+
540
+ ### B.8 Common pitfalls
541
+
542
+ - **`sdd approve --inline` deprecation.** The `--inline` flag (legacy mode that writes `approval_record` directly into spec) prints a deprecation warning and is removed in v1.1.0 of the CLI Surface. Switch to plan mode (default) to avoid breaking changes.
543
+ - **Self-approval.** `sdd approve --approver <agent-id>` (Claude, Codex, `bot:*`, `sdd-cli`) is rejected with `agent-approver` reason. Always use a human identity.
544
+ - **`sdd refresh` writes nothing.** It emits stubs to stdout; apply manually. Auto-application would violate INV-002 («CLI is read-only on spec»).
545
+ - **Plan files in `.sdd/plans/`.** By default not committed (`*.yaml` belongs in `.gitignore`). For git-audited consumers, commit explicitly.
546
+ - **`@covers` partition prefix.** Must match `^[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)*$`. Uppercase or other characters cause silent skip (OQ-017 in sdd-cli spec).
547
+ - **Frequent `sdd doctor` drift.** After any minor bump in `agent-sdd`, `compatible_sdd_cli` in `enforcement_registry.md` must be reviewed and updated. Without this discipline `sdd doctor` keeps reporting `version_mismatch` after every CLI release.
548
+
549
+ ### B.9 Reference
550
+
551
+ - `rules/sdd-cli-usage.md` — phase-to-command mapping, exit-code taxonomy, troubleshooting table.
552
+ - `rules/enforcement_registry.md` — which SDD requirement is closed by which command.
553
+ - sdd-cli's own spec: `<global-node-modules>/agent-sdd/spec/spec.md` for canonical contracts (resolve `<global-node-modules>` via `npm root -g`).
554
+ - sdd-cli README: full CLI manual including config schema and exit codes.
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "absolute": [
4
+ "возможно",
5
+ "вероятно",
6
+ "обычно",
7
+ "as a rule",
8
+ "etc.",
9
+ "and so on",
10
+ "should usually",
11
+ "similar to",
12
+ "approximately",
13
+ "best-effort",
14
+ "best effort",
15
+ "informally"
16
+ ],
17
+ "modal_in_normative": [
18
+ "may be",
19
+ "might be"
20
+ ],
21
+ "notes": "Absolute words: blocked in any normative section (NORMATIVE_SECTIONS). Modal phrases: blocked only in template fields marked is_normative=true (see TemplateFieldMetadata.IS_NORMATIVE in @cyberash/sdd-cli); allowed in Context/Glossary/comments and in Scenario prose with explicit options. List is byte-equivalent with @cyberash/sdd-cli@1.0.0 src/shared/domain/WeaselWords.ts; CLI's tests/unit/weasel-words-sync.test.ts asserts the equivalence. Expanding (e.g. adding 'as needed', 'where appropriate', singletons 'may'/'might') requires a coordinated PR in both repos plus a minor bump on @cyberash/sdd-cli/diagnostics."
22
+ }