@starci/hfs 4.0.8 → 4.0.10

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 (703) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +81 -39
  4. package/emit/contracts.mjs +56 -9
  5. package/emit/db-types.mjs +169 -0
  6. package/emit/events.mjs +41 -0
  7. package/emit/operations-worker.mjs +1 -1
  8. package/emit/operations.mjs +2 -2
  9. package/emit/schema-worker.mjs +1 -1
  10. package/lint/bound-sys.cjs +50 -0
  11. package/lint/run.mjs +131 -24
  12. package/package.json +16 -10
  13. package/report/sonar.mjs +8 -8
  14. package/runtime/engine/admission.mjs +308 -0
  15. package/runtime/engine/canonical-json.mjs +10 -0
  16. package/runtime/engine/config.mjs +486 -0
  17. package/runtime/engine/db/blob.mjs +315 -0
  18. package/runtime/engine/db/ledger-paths.mjs +83 -0
  19. package/runtime/engine/db/ledger.mjs +1118 -0
  20. package/runtime/engine/db/machine-connection.mjs +135 -0
  21. package/runtime/engine/db/machine-schema.mjs +84 -0
  22. package/runtime/engine/db/machine.mjs +1367 -0
  23. package/runtime/engine/db/migrations/machine/0001-init.sql +924 -0
  24. package/runtime/engine/db/migrations/runtime/0001-init.sql +1108 -0
  25. package/runtime/engine/db/provider-reservations.mjs +101 -0
  26. package/runtime/engine/digest.mjs +16 -0
  27. package/runtime/engine/invalid-config.mjs +15 -0
  28. package/runtime/engine/orca-config.mjs +27 -0
  29. package/runtime/engine/refuse.mjs +11 -0
  30. package/runtime/engine/secrets.mjs +130 -0
  31. package/runtime/knowledge/hfs/canon-pins.yaml +162 -31
  32. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
  33. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  34. package/runtime/knowledge/hfs/slots.yaml +912 -120
  35. package/runtime/knowledge/patterns/be/api.yaml +369 -0
  36. package/runtime/knowledge/patterns/be/cli.yaml +297 -0
  37. package/runtime/knowledge/patterns/be/domain.yaml +113 -0
  38. package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
  39. package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
  40. package/runtime/knowledge/patterns/be/projections.yaml +178 -0
  41. package/runtime/knowledge/patterns/be/queues.yaml +236 -0
  42. package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
  43. package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
  44. package/runtime/knowledge/patterns/be/saga.yaml +248 -0
  45. package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
  46. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  47. package/runtime/knowledge/sonar-gate.yaml +52 -18
  48. package/runtime/modules/kernel/failure-codes.yaml +611 -51
  49. package/runtime/modules/models/registry.yaml +553 -0
  50. package/runtime/modules/models/runtimes.yaml +695 -0
  51. package/runtime/modules/ops/_labels.yaml +53 -0
  52. package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
  53. package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
  54. package/runtime/scripts/api/fs/lib.mjs +16 -2
  55. package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
  56. package/runtime/scripts/api/git/config-get.mjs +10 -0
  57. package/runtime/scripts/api/git/diff.mjs +5 -0
  58. package/runtime/scripts/api/git/lib.mjs +18 -26
  59. package/runtime/scripts/api/git/log.mjs +5 -0
  60. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  61. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  62. package/runtime/scripts/api/git/merge-base.mjs +9 -0
  63. package/runtime/scripts/api/git/remote.mjs +5 -0
  64. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  65. package/runtime/scripts/api/git/show.mjs +6 -0
  66. package/runtime/scripts/api/node/lib.mjs +14 -0
  67. package/runtime/scripts/api/node/spawn-node.mjs +6 -0
  68. package/runtime/scripts/api/process/lib.mjs +111 -0
  69. package/runtime/scripts/api/process/owned-process.mjs +78 -0
  70. package/runtime/scripts/api/process/resolve-real-tool.mjs +46 -0
  71. package/runtime/scripts/api/process/run-program.mjs +7 -0
  72. package/runtime/scripts/api/process/stop-owned-process.mjs +9 -0
  73. package/runtime/scripts/api/sops/decrypt.mjs +9 -12
  74. package/runtime/scripts/api/sops/lib.mjs +255 -0
  75. package/runtime/scripts/api/sops/seal.mjs +22 -0
  76. package/runtime/scripts/connectors/lib.mjs +488 -0
  77. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  78. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  79. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  80. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  81. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  82. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  83. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  84. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  85. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  86. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  87. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  88. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  89. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  90. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  91. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  92. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  93. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  94. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -2
  95. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  96. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  97. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  98. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  99. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  100. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  101. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  102. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  103. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  104. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  105. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  106. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  107. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  108. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  109. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  110. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  111. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  112. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  113. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  114. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  115. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  116. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  117. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  118. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  119. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  120. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  121. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  122. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  123. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  124. package/runtime/scripts/hfs/architecture.mjs +4 -4
  125. package/runtime/scripts/hfs/check.mjs +75 -72
  126. package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
  127. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  128. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  129. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  130. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  131. package/runtime/scripts/hfs/param-names.mjs +18 -0
  132. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  133. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  134. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  135. package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
  136. package/runtime/scripts/hfs/rules/cli.mjs +37 -0
  137. package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
  138. package/runtime/scripts/hfs/rules/contract.mjs +10 -9
  139. package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
  140. package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
  141. package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
  142. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
  143. package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
  144. package/runtime/scripts/hfs/rules/database.mjs +68 -0
  145. package/runtime/scripts/hfs/rules/deps.mjs +1 -1
  146. package/runtime/scripts/hfs/rules/docker.mjs +259 -0
  147. package/runtime/scripts/hfs/rules/edition.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
  149. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
  150. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  151. package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
  152. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
  153. package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
  154. package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
  155. package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
  156. package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
  157. package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
  158. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
  159. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  160. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
  161. package/runtime/scripts/hfs/rules/saga.mjs +170 -0
  162. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  163. package/runtime/scripts/hfs/rules/services.mjs +220 -0
  164. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  165. package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
  166. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
  167. package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
  168. package/runtime/scripts/hfs/secret.mjs +209 -0
  169. package/runtime/scripts/hfs/slots.mjs +78 -108
  170. package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
  171. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  172. package/runtime/scripts/hfs/tree.mjs +5 -4
  173. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  174. package/runtime/scripts/hfs/view.mjs +3 -2
  175. package/runtime/scripts/lib/clip.mjs +19 -0
  176. package/runtime/scripts/lib/display-names.mjs +259 -0
  177. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  178. package/runtime/scripts/lib/env.mjs +10 -0
  179. package/runtime/scripts/lib/event-contract.mjs +103 -0
  180. package/runtime/scripts/lib/example-refs.mjs +158 -0
  181. package/runtime/scripts/lib/fs-kind.mjs +14 -4
  182. package/runtime/scripts/lib/git.mjs +36 -0
  183. package/runtime/scripts/lib/graphql-contract.mjs +1 -1
  184. package/runtime/scripts/lib/i18n.mjs +86 -0
  185. package/runtime/scripts/lib/json-schema.mjs +52 -0
  186. package/runtime/scripts/lib/json.mjs +13 -3
  187. package/runtime/scripts/lib/language.mjs +24 -5
  188. package/runtime/scripts/lib/list.mjs +20 -0
  189. package/runtime/scripts/lib/mutation-fence.mjs +15 -0
  190. package/runtime/scripts/lib/package-at.mjs +37 -0
  191. package/runtime/scripts/lib/path-key.mjs +57 -2
  192. package/runtime/scripts/lib/process-identity.mjs +6 -0
  193. package/runtime/scripts/lib/read-text.mjs +34 -0
  194. package/runtime/scripts/lib/read-yaml.mjs +18 -0
  195. package/runtime/scripts/lib/redact.mjs +161 -0
  196. package/runtime/scripts/lib/regex.mjs +4 -0
  197. package/runtime/scripts/lib/same-text.mjs +5 -0
  198. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  199. package/runtime/scripts/lib/sleep-sync.mjs +2 -1
  200. package/runtime/scripts/lib/sleep.mjs +7 -0
  201. package/runtime/scripts/lib/sops-envelope.mjs +112 -0
  202. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  203. package/runtime/scripts/lib/sqlite.mjs +21 -0
  204. package/runtime/scripts/lib/stack-services.mjs +7 -31
  205. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  206. package/runtime/scripts/lib/walk.mjs +4 -3
  207. package/scaffold/add-app.mjs +174 -0
  208. package/scaffold/add-cli-lite.mjs +257 -0
  209. package/scaffold/add-inbox-lite.mjs +314 -0
  210. package/scaffold/add-table.mjs +344 -0
  211. package/scaffold/add.mjs +299 -0
  212. package/scaffold/app.mjs +358 -69
  213. package/scaffold/edition-gate.mjs +25 -0
  214. package/scaffold/image.mjs +19 -0
  215. package/scaffold/lite-exports.mjs +186 -0
  216. package/scaffold/service.mjs +32 -26
  217. package/scaffold/supabase-ports.mjs +26 -0
  218. package/src/main.mjs +327 -0
  219. package/sync/cli.mjs +6 -9
  220. package/sync/format.mjs +1 -1
  221. package/sync/hygiene.mjs +10 -10
  222. package/sync/index.mjs +167 -104
  223. package/sync/managed.mjs +19 -11
  224. package/sync/sonar-key.mjs +5 -0
  225. package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
  226. package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
  227. package/templates/app/ci-workflows/sonar-steps.yml +9 -0
  228. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
  229. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
  230. package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
  231. package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
  232. package/templates/app/docker-ignore/dockerignore +50 -0
  233. package/templates/app/gitignore-lite +35 -0
  234. package/templates/app/hooks/husky/pre-commit +3 -8
  235. package/templates/app/hooks/husky/pre-push +45 -6
  236. package/templates/app/package-scripts/core.json +8 -0
  237. package/templates/app/package-scripts/package.json +3 -10
  238. package/templates/app/package-scripts-lite/package.json +15 -0
  239. package/templates/app/quality-config/codecov.yml +15 -3
  240. package/templates/app/quality-config/sonar-base.properties +4 -0
  241. package/templates/app/quality-config/sonar-project.properties +1 -4
  242. package/templates/app/quality-config-lite/sonar-project.properties +5 -0
  243. package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
  244. package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
  245. package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
  246. package/templates/app/skeleton/README.md +7 -5
  247. package/templates/app/skeleton-lite/.sops.yaml +3 -0
  248. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
  249. package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
  250. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
  251. package/templates/app/skeleton-lite/README.md +40 -0
  252. package/templates/app/skeleton-lite/supabase/config.toml +41 -0
  253. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
  254. package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
  255. package/templates/app/task-graph/turbo.json +23 -0
  256. package/templates/app/tool-config-lite/prettierignore +2 -0
  257. package/templates/app/tool-config-lite/prettierrc +1 -0
  258. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
  259. package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
  260. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
  261. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
  262. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
  263. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
  264. package/templates/be/common/main-listen.ts.partial +14 -0
  265. package/templates/be/common/sql-ident.ts.partial +7 -0
  266. package/templates/be/common/sql-tag.ts.partial +5 -0
  267. package/templates/be/image/api/Dockerfile +38 -0
  268. package/templates/be/image/cli/Dockerfile +38 -0
  269. package/templates/be/image/worker/Dockerfile +36 -0
  270. package/templates/be/inbox/database-client.ts.tpl +50 -0
  271. package/templates/be/inbox/index.ts.tpl +2 -0
  272. package/templates/be/inbox/migration.sql.tpl +29 -0
  273. package/templates/be/inbox/module-definition.ts.tpl +6 -0
  274. package/templates/be/inbox/module.ts.tpl +14 -0
  275. package/templates/be/inbox/options.ts.tpl +2 -0
  276. package/templates/be/inbox/service.ts.tpl +33 -0
  277. package/templates/be/inbox/sql.ts.tpl +16 -0
  278. package/templates/be/inbox/webhook-door.ts.tpl +36 -0
  279. package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
  280. package/templates/be/patterns/api/application.command.ts.tpl +10 -0
  281. package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
  282. package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
  283. package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
  284. package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
  285. package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
  286. package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
  287. package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
  288. package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
  289. package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
  290. package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
  291. package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
  292. package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
  293. package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
  294. package/templates/be/patterns/api/http.module.ts.tpl +7 -0
  295. package/templates/be/patterns/api/http.request.ts.tpl +10 -0
  296. package/templates/be/patterns/api/http.response.ts.tpl +5 -0
  297. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
  298. package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
  299. package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
  300. package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
  301. package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
  302. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
  303. package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
  304. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
  305. package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
  306. package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
  307. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
  308. package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
  309. package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
  310. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
  311. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
  312. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
  313. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
  314. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
  315. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
  316. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
  317. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
  318. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
  319. package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
  320. package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
  321. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
  322. package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
  323. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
  324. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
  325. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
  326. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
  327. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
  328. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
  329. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
  330. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
  331. package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
  332. package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
  333. package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
  334. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
  335. package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
  336. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
  337. package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
  338. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
  339. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
  340. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
  341. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
  342. package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
  343. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
  344. package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
  345. package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
  346. package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
  347. package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
  348. package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
  349. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
  350. package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
  351. package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
  352. package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
  353. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
  354. package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
  355. package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
  356. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
  357. package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
  358. package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
  359. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
  360. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
  361. package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
  362. package/templates/be/patterns/projections/migration.ts.tpl +18 -0
  363. package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
  364. package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
  365. package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
  366. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
  367. package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
  368. package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
  369. package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
  370. package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
  371. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
  372. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
  373. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
  374. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
  375. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
  376. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
  377. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
  378. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
  379. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
  380. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
  381. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
  382. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
  383. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
  384. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
  385. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
  386. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
  387. package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
  388. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
  389. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
  390. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
  391. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
  392. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
  393. package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
  394. package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
  395. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
  396. package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
  397. package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
  398. package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
  399. package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
  400. package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
  401. package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
  402. package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
  403. package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
  404. package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
  405. package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
  406. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
  407. package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
  408. package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
  409. package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
  410. package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
  411. package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
  412. package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
  413. package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
  414. package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
  415. package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
  416. package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
  417. package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
  418. package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
  419. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
  420. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
  421. package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
  422. package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
  423. package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
  424. package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
  425. package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
  426. package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
  427. package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
  428. package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
  429. package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
  430. package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
  431. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
  432. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
  433. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
  434. package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
  435. package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
  436. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  437. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
  438. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
  439. package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
  440. package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
  441. package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
  442. package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
  443. package/templates/be/skeleton/src/features/cli/index.ts +1 -0
  444. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
  445. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
  446. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
  447. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
  448. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
  449. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
  450. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
  451. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
  452. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
  453. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
  454. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
  455. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
  456. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
  457. package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
  458. package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
  459. package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
  460. package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
  461. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
  462. package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
  463. package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
  464. package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
  465. package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
  466. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
  467. package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
  468. package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
  469. package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
  470. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
  471. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
  472. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  473. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
  474. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
  475. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
  476. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
  477. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
  478. package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
  479. package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
  480. package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
  481. package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
  482. package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
  483. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
  484. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
  485. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
  486. package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
  487. package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
  488. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
  489. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
  490. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
  491. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
  492. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
  493. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
  494. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
  495. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
  496. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
  497. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
  498. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
  499. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
  500. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
  501. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
  502. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
  503. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
  504. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
  505. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
  506. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
  507. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
  508. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
  509. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
  510. package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
  511. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
  512. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
  513. package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
  514. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
  515. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
  516. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
  517. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
  518. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
  519. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
  520. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
  521. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
  522. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
  523. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
  524. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
  525. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
  526. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
  527. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
  528. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
  529. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
  530. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
  531. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
  532. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
  533. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
  534. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
  535. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
  536. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
  537. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
  538. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
  539. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
  540. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
  541. package/templates/be/table/error.ts.tpl +16 -0
  542. package/templates/be/table/index.ts.tpl +2 -0
  543. package/templates/be/table/migration.sql.tpl +51 -0
  544. package/templates/be/table/module-definition.ts.tpl +7 -0
  545. package/templates/be/table/module.ts.tpl +14 -0
  546. package/templates/be/table/options.ts.tpl +2 -0
  547. package/templates/be/table/rows.ts.tpl +4 -0
  548. package/templates/be/table/service.ts.tpl +23 -0
  549. package/templates/be/table/sql.ts.tpl +4 -0
  550. package/templates/be/tool-config/jest.config.js +3 -1
  551. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
  552. package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
  553. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
  554. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
  555. package/templates/common/gitignore.base +1 -2
  556. package/templates/fe/app/package.json.tpl +1 -0
  557. package/templates/fe/app/tsconfig.json +12 -0
  558. package/templates/fe/common/app-layout.tsx.partial +8 -0
  559. package/templates/fe/common/site-shell.tsx.partial +9 -0
  560. package/templates/fe/image/next/Dockerfile +42 -0
  561. package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
  562. package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
  563. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
  564. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
  565. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
  566. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
  567. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
  568. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
  569. package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
  570. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
  571. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
  572. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
  573. package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
  574. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
  575. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
  576. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
  577. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  578. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
  579. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
  580. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
  581. package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
  582. package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
  583. package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
  584. package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
  585. package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
  586. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
  587. package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
  588. package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
  589. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
  590. package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
  591. package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
  592. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
  593. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
  594. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
  595. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
  596. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
  597. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
  598. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
  599. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
  600. package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
  601. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
  602. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
  603. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
  604. package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
  605. package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
  606. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
  607. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
  608. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
  609. package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
  610. package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
  611. package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
  612. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
  613. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
  614. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
  615. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
  616. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
  617. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
  618. package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
  619. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
  620. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
  621. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
  622. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
  623. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
  624. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
  625. package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
  626. package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
  627. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
  628. package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
  629. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
  630. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
  631. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
  632. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
  633. package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
  634. package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
  635. package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
  636. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
  637. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
  638. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
  639. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
  640. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
  641. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
  642. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
  643. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
  644. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
  645. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
  646. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  647. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
  648. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
  649. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
  650. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
  651. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
  652. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
  653. package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
  654. package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
  655. package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
  656. package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
  657. package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
  658. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
  659. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
  660. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
  661. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
  662. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
  663. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
  664. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
  665. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
  666. package/templates/fe/table/read.ts.tpl +16 -0
  667. package/templates/fe/table/write.ts.tpl +16 -0
  668. package/upgrade/check.mjs +137 -0
  669. package/upgrade/index.mjs +524 -0
  670. package/bin/hfs.mjs +0 -233
  671. package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
  672. package/runtime/scripts/lib/test-secrets.mjs +0 -116
  673. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
  674. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
  675. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
  676. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
  677. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
  678. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
  679. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
  680. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
  681. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
  682. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
  683. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
  684. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
  685. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
  686. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
  687. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
  688. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
  689. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
  690. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
  691. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
  692. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
  693. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
  694. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
  695. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
  696. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
  697. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
  698. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
  699. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
  700. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
  701. /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
  702. /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
  703. /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
@@ -0,0 +1,1108 @@
1
+ -- ############################################################################################################
2
+ -- runtime.sqlite (one per project) - this file is the single schema step of the database.
3
+ -- Tables: meta/schema_migrations - identity and migration journal; ui_states/ui_state_map - display vocabulary;
4
+ -- blob_ref_columns - blob-referencing columns; workflow_transitions/job_transitions - legal state changes;
5
+ -- blobs - content-store index; workflows/lifecycle_changes/goals/goal_inputs/workflow_purges - workflow and goal;
6
+ -- work_graph_versions/work_units/unit_edges - work graph; jobs/op_attempts/contracts/resources/leases/
7
+ -- api_requests - tries, dispatches, fencing, idempotency; reports/check_runs/settle_tails/product_lands/
8
+ -- job_artifacts/attempt_transcript_snapshots/report_attachments/artifact_proofs/work_citations/
9
+ -- interface_audits/llm_usage - outcomes and evidence; inbox/decision_items/decisions/conditions/incidents/
10
+ -- foundations/foundation_declarations/path_transfers/record_changes/signals - coordination; events/logs/
11
+ -- log_cursors - append-only journal and typed logs; v_* - durable read views.
12
+ -- ############################################################################################################
13
+
14
+ -- ---------------------------------------------------------------------------------------------------------
15
+ -- A0. Identity, version, vocabulary
16
+ -- ---------------------------------------------------------------------------------------------------------
17
+ -- meta: ledger_id (UUID, key of machine.ledgers), schema='starci/runtime@1', created_at, journal_mode, product,
18
+ -- repo_root, blob_root, runtime_rev, sqlite_version.
19
+ CREATE TABLE IF NOT EXISTS meta(
20
+ key TEXT PRIMARY KEY,
21
+ value TEXT NOT NULL) STRICT;
22
+
23
+ -- schema_migrations: 0001-init writes the first row; each later step: backup VACUUM INTO + integrity_check first,
24
+ -- foreign_key_check + quick_check before COMMIT. Code refuses to WRITE when user_version > CODE_VERSION.
25
+ CREATE TABLE IF NOT EXISTS schema_migrations(
26
+ version INTEGER PRIMARY KEY,
27
+ name TEXT NOT NULL, -- '0001-init'
28
+ runtime_rev TEXT,
29
+ sql_sha256 TEXT NOT NULL, -- digest of the migration file that ran
30
+ backup_path TEXT, backup_sha256 TEXT,
31
+ started_at INTEGER NOT NULL,
32
+ finished_at INTEGER,
33
+ status TEXT NOT NULL CHECK(status IN ('running','done','failed'))) STRICT;
34
+
35
+ -- ui_states: the ONLY set of display states (UI-API sec. 3.4).
36
+ CREATE TABLE IF NOT EXISTS ui_states(
37
+ ui TEXT PRIMARY KEY CHECK(ui IN ('bad','warn','running','waiting','ok','done','unknown')),
38
+ rank INTEGER NOT NULL) STRICT; -- sort order: bad first
39
+ INSERT OR IGNORE INTO ui_states VALUES
40
+ ('bad',0),('warn',1),('running',2),('waiting',3),('ok',4),('done',5),('unknown',6);
41
+
42
+ -- ui_state_map: static mapping of native state -> ui. Time-based rules (overdue, SLA) live in views/functions and
43
+ -- may only RAISE the level (waiting->warn->bad), never lower it.
44
+ CREATE TABLE IF NOT EXISTS ui_state_map(
45
+ entity TEXT NOT NULL, native TEXT NOT NULL,
46
+ ui TEXT NOT NULL REFERENCES ui_states(ui),
47
+ PRIMARY KEY(entity,native)) STRICT;
48
+ INSERT OR IGNORE INTO ui_state_map VALUES
49
+ ('workflow','awaiting-approval','waiting'),('workflow','queued','waiting'),('workflow','running','running'),
50
+ ('workflow','paused','waiting'),('workflow','stopped','done'),('workflow','finished','done'),('workflow','archived','done'),
51
+ ('unit','planned','waiting'),('unit','queued','waiting'),('unit','deciding','waiting'),('unit','running','running'),
52
+ ('unit','reported','running'),('unit','failed','bad'),('unit','done','done'),('unit','dropped','done'),
53
+ ('job','queued','waiting'),('job','ready','waiting'),('job','leased','running'),('job','running','running'),
54
+ ('job','answering','waiting'),('job','reported','running'),('job','deciding','waiting'),('job','awaiting_owner','waiting'),
55
+ ('job','effect_unknown','bad'),('job','succeeded','done'),('job','failed','bad'),('job','cancelled','done'),
56
+ ('attempt','routed','waiting'),('attempt','in-flight','running'),('attempt','pass','done'),('attempt','partial','warn'),
57
+ ('attempt','fail','bad'),('attempt','blocked','bad'),('attempt','worker-dead','bad'),('attempt','effect-unknown','bad'),
58
+ ('attempt','requeued','done'),('attempt','cancelled','done'),('attempt','dropped','done'),
59
+ ('check','pass','ok'),('check','fail','bad'),('check','error','bad'),('check','unavailable','warn'),('check','skipped','waiting'),
60
+ ('decision','open','waiting'),('decision','claimed','running'),('decision','escalated','warn'),('decision','expired','warn'),
61
+ ('decision','resolved','done'),('decision','superseded','done'),
62
+ ('incident','open','bad'),('incident','resolved','done'),('incident','superseded','done'),
63
+ ('condition','True','ok'),('condition','False','bad'),('condition','Unknown','waiting'),
64
+ ('settle-tail','queued','waiting'),('settle-tail','running','running'),('settle-tail','done','done'),('settle-tail','failed','bad'),
65
+ ('product-land','queued','waiting'),('product-land','landed','done'),('product-land','failed','bad'),('product-land','conflict','bad'),
66
+ ('product-land','red','bad'),('product-land','busy','warn'),('product-land','main-moving','warn');
67
+
68
+ -- blob_ref_columns: the ONLY list of blob-referencing columns (GC mark reads this table; a spec diffs every
69
+ -- *_sha / sha256 column against sqlite_master so no new column is missed).
70
+ CREATE TABLE IF NOT EXISTS blob_ref_columns(
71
+ table_name TEXT NOT NULL, column_name TEXT NOT NULL, PRIMARY KEY(table_name,column_name)) STRICT;
72
+ INSERT OR IGNORE INTO blob_ref_columns VALUES
73
+ ('goal_inputs','sha256'),('op_attempts','prompt_sha'),('op_attempts','transcript_sha'),('op_attempts','session_sha'),
74
+ ('attempt_transcript_snapshots','sha256'),
75
+ ('check_runs','stdout_sha'),('check_runs','stderr_sha'),('check_runs','output_sha'),('job_artifacts','sha256'),
76
+ ('work_citations','sha256'),('product_lands','output_sha'),('events','payload_sha');
77
+
78
+ -- Valid state-transition tables (data, not code). Triggers in A2/A3/A4 refuse any pair not listed here.
79
+ CREATE TABLE IF NOT EXISTS workflow_transitions(
80
+ from_phase TEXT NOT NULL, to_phase TEXT NOT NULL, PRIMARY KEY(from_phase,to_phase)) STRICT;
81
+ INSERT OR IGNORE INTO workflow_transitions VALUES
82
+ ('awaiting-approval','queued'),('awaiting-approval','stopped'),
83
+ ('queued','running'),('queued','stopped'),
84
+ ('running','paused'),('running','stopped'),('running','finished'),
85
+ ('paused','running'),('paused','stopped'),
86
+ ('stopped','queued'), -- only the owner verb `starci kernel lifecycle --resume`; a controller NEVER does this (MB-08)
87
+ ('stopped','archived'),('finished','archived');
88
+ CREATE TABLE IF NOT EXISTS job_transitions(
89
+ from_status TEXT NOT NULL, to_status TEXT NOT NULL, PRIMARY KEY(from_status,to_status)) STRICT;
90
+ INSERT OR IGNORE INTO job_transitions VALUES
91
+ ('queued','ready'),('queued','cancelled'),
92
+ ('ready','queued'),('ready','leased'),('ready','cancelled'),
93
+ ('leased','running'),('leased','ready'),('leased','cancelled'), -- leased->ready: dispatch refused before the op accepts the contract (H13: spends no try)
94
+ ('leased','failed'), -- leased->failed: settling a dispatch that died mid-launch after terminal/attempt were created
95
+ ('running','answering'),('answering','running'),
96
+ ('running','reported'),('answering','reported'),
97
+ ('running','effect_unknown'),('running','ready'),('running','failed'),('running','cancelled'), -- running->ready: requeue after worker death
98
+ ('effect_unknown','running'),('effect_unknown','ready'),('effect_unknown','failed'),
99
+ ('reported','deciding'),('reported','succeeded'),('reported','failed'), -- reported NEVER -> cancelled (H9: archive does not drop reported work)
100
+ ('deciding','succeeded'),('deciding','failed'),('deciding','cancelled'),
101
+ ('reported','awaiting_owner'),('deciding','awaiting_owner'); -- an op may end with a question: waiting on the owner, not a failure (awaiting_owner is terminal, like failed)
102
+
103
+ -- ---------------------------------------------------------------------------------------------------------
104
+ -- A1. Blob - index of the content store (content is NOT in SQLite)
105
+ -- ---------------------------------------------------------------------------------------------------------
106
+ CREATE TABLE IF NOT EXISTS blobs(
107
+ sha256 TEXT PRIMARY KEY CHECK(length(sha256)=64),
108
+ bytes INTEGER NOT NULL CHECK(bytes>=0),
109
+ media_type TEXT NOT NULL, -- image/png, video/webm, application/json, text/plain, text/x-diff ...
110
+ encoding TEXT CHECK(encoding IS NULL OR encoding='gzip'), -- sha256 is always of the ORIGINAL bytes
111
+ redaction TEXT CHECK(redaction IS NULL OR redaction IN ('v1','binary')), -- 'v1' = text passed through secret filter v1
112
+ -- (transcript, log); 'binary' = binary that cannot be filtered; NULL = not needed
113
+ file_uri TEXT NOT NULL, -- absolute path, e.g. <blob_root>/ab/ab12...
114
+ http_path TEXT GENERATED ALWAYS AS ('/api/blob/' || sha256) VIRTUAL, -- served by the harness; cannot drift
115
+ created_at INTEGER NOT NULL, -- GC never touches a blob younger than 24 hours (put-before-insert grace)
116
+ pinned INTEGER NOT NULL DEFAULT 0 CHECK(pinned IN (0,1)), -- 1 once a Work record cites it: never swept
117
+ archived_at INTEGER, -- already inside a verified zip in the archive store
118
+ archive_ref TEXT) STRICT; -- '<zip path>!<entry>'
119
+
120
+ -- ---------------------------------------------------------------------------------------------------------
121
+ -- A2. Workflow and goal
122
+ -- ---------------------------------------------------------------------------------------------------------
123
+ CREATE TABLE IF NOT EXISTS workflows(
124
+ workflow_id TEXT PRIMARY KEY, -- wf-<slug>-<base36 time>
125
+ trace_id TEXT NOT NULL UNIQUE CHECK(length(trace_id)=32), -- W3C trace id, minted at creation, kept across revisions
126
+ title TEXT,
127
+ display_name TEXT, -- '<Product> - <what>' (Vietnamese)
128
+ phase TEXT NOT NULL CHECK(phase IN ('awaiting-approval','queued','running','paused','stopped','finished','archived')),
129
+ phase_reason TEXT, -- reason for the latest phase change (detail in lifecycle_changes)
130
+ ledger_mode TEXT,
131
+ source_roots_json TEXT CHECK(source_roots_json IS NULL OR json_valid(source_roots_json)),
132
+ generation INTEGER NOT NULL DEFAULT 0, -- spec: bumped when the goal/graph changes
133
+ observed_generation INTEGER NOT NULL DEFAULT 0, -- status: the generation the Kernel has fully processed (K8s)
134
+ goal_identity TEXT,
135
+ pin_digest TEXT,
136
+ allowed_parallel INTEGER,
137
+ finished_json TEXT CHECK(finished_json IS NULL OR json_valid(finished_json)),
138
+ created_at INTEGER NOT NULL,
139
+ updated_at INTEGER NOT NULL,
140
+ finished_at INTEGER,
141
+ archived_at INTEGER) STRICT;
142
+
143
+ -- lifecycle_changes: phase-change history, append-only (MB-08, G10). Sole writer: the `starci kernel lifecycle` verb
144
+ -- (define-goal, start, pause, stop, resume, finish, archive) - same transaction as UPDATE workflows.phase.
145
+ CREATE TABLE IF NOT EXISTS lifecycle_changes(
146
+ change_id INTEGER PRIMARY KEY AUTOINCREMENT,
147
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
148
+ from_phase TEXT, to_phase TEXT NOT NULL,
149
+ by TEXT NOT NULL, -- owner | kernel:<wf> | supervisor
150
+ reason TEXT NOT NULL,
151
+ at INTEGER NOT NULL) STRICT;
152
+ CREATE TRIGGER IF NOT EXISTS lifecycle_changes_append_only BEFORE UPDATE ON lifecycle_changes BEGIN
153
+ SELECT RAISE(ABORT,'lifecycle_changes are append-only');
154
+ END;
155
+ -- Phase may only change per workflow_transitions AND a matching lifecycle_changes row must be written just before, in the same transaction.
156
+ CREATE TRIGGER IF NOT EXISTS workflows_phase_guard BEFORE UPDATE OF phase ON workflows
157
+ WHEN NEW.phase <> OLD.phase BEGIN
158
+ SELECT RAISE(ABORT,'workflow-transition-refused')
159
+ WHERE NOT EXISTS(SELECT 1 FROM workflow_transitions t WHERE t.from_phase=OLD.phase AND t.to_phase=NEW.phase);
160
+ SELECT RAISE(ABORT,'workflow-transition-unrecorded')
161
+ WHERE NOT EXISTS(SELECT 1 FROM lifecycle_changes c WHERE c.workflow_id=NEW.workflow_id
162
+ AND c.from_phase IS OLD.phase AND c.to_phase=NEW.phase
163
+ AND c.change_id=(SELECT max(change_id) FROM lifecycle_changes WHERE workflow_id=NEW.workflow_id));
164
+ END;
165
+
166
+ CREATE TABLE IF NOT EXISTS goals(
167
+ goal_seq INTEGER PRIMARY KEY AUTOINCREMENT,
168
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
169
+ revision INTEGER NOT NULL,
170
+ goal_identity TEXT NOT NULL,
171
+ markdown TEXT NOT NULL, -- goal text the owner approved (relaunch source)
172
+ json TEXT NOT NULL CHECK(json_valid(json)), -- derived op chain
173
+ amendment_json TEXT CHECK(amendment_json IS NULL OR json_valid(amendment_json)),
174
+ approved_by TEXT, approval_ref TEXT,
175
+ created_at INTEGER NOT NULL,
176
+ UNIQUE(workflow_id,revision)) STRICT;
177
+
178
+ CREATE TABLE IF NOT EXISTS goal_inputs(
179
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
180
+ key TEXT NOT NULL, -- '<index>-<basename>'
181
+ goal_revision INTEGER NOT NULL,
182
+ sha256 TEXT NOT NULL REFERENCES blobs(sha256),
183
+ origin TEXT NOT NULL,
184
+ created_at INTEGER NOT NULL,
185
+ PRIMARY KEY(workflow_id,key)) STRICT;
186
+
187
+ -- workflow_purges: tombstone of the single delete path (no FK: survives the purge).
188
+ CREATE TABLE IF NOT EXISTS workflow_purges(
189
+ workflow_id TEXT PRIMARY KEY,
190
+ state TEXT NOT NULL CHECK(state IN ('planned','archived','deleting','purged')),
191
+ approved_by TEXT, approval_ref TEXT,
192
+ archive_path TEXT, archive_sha256 TEXT, archive_bytes INTEGER, manifest_sha256 TEXT,
193
+ events_head TEXT,
194
+ counts_json TEXT CHECK(counts_json IS NULL OR json_valid(counts_json)),
195
+ created_at INTEGER NOT NULL, archived_at INTEGER, verified_at INTEGER, purged_at INTEGER,
196
+ CHECK(state IN ('planned','archived') OR (approved_by IS NOT NULL AND approval_ref IS NOT NULL
197
+ AND archive_path IS NOT NULL AND archive_sha256 IS NOT NULL AND verified_at IS NOT NULL))) STRICT;
198
+
199
+ -- ---------------------------------------------------------------------------------------------------------
200
+ -- A3. Graph: units (steps) and edges
201
+ -- Vocabulary: unit = logical step; job = one try with concrete input (try_no); attempt = one dispatch (op_attempts).
202
+ -- ---------------------------------------------------------------------------------------------------------
203
+ CREATE TABLE IF NOT EXISTS work_graph_versions(
204
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
205
+ version INTEGER NOT NULL CHECK(version>=0),
206
+ event TEXT NOT NULL CHECK(event IN ('draw','revise','cut','edit')),
207
+ graph_json TEXT NOT NULL CHECK(json_valid(graph_json)),
208
+ diff_json TEXT NOT NULL CHECK(json_valid(diff_json)),
209
+ colors_json TEXT NOT NULL CHECK(json_valid(colors_json)),
210
+ reason TEXT NOT NULL, author_op TEXT NOT NULL, author_job TEXT, digest TEXT NOT NULL,
211
+ created_at INTEGER NOT NULL,
212
+ PRIMARY KEY(workflow_id,version)) STRICT;
213
+
214
+ CREATE TABLE IF NOT EXISTS work_units(
215
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
216
+ unit_id TEXT NOT NULL, -- = job_id of the first job in the family
217
+ op_id TEXT NOT NULL,
218
+ subject_key TEXT NOT NULL, -- work identity, normalized: '<cut_id>#<ordinal>' or digest(records + owned_paths)
219
+ goal_revision INTEGER NOT NULL,
220
+ title TEXT,
221
+ cut_id TEXT, cut_ordinal INTEGER, cut_total INTEGER,
222
+ repository TEXT,
223
+ state TEXT NOT NULL CHECK(state IN ('planned','queued','running','reported','deciding','done','failed','dropped')),
224
+ current_job_id TEXT,
225
+ tries INTEGER NOT NULL DEFAULT 0, -- number of jobs in the family
226
+ dispatches INTEGER NOT NULL DEFAULT 0, -- cumulative dispatch count
227
+ try_budget INTEGER NOT NULL DEFAULT 5 CHECK(try_budget>=1), -- H3: exceeding it makes INSERT jobs refuse
228
+ budget_raised_by TEXT, -- who raised the budget (owner | supervisor) ...
229
+ budget_raised_ref TEXT, -- ... and why (di_id / incident_id); required when try_budget > default
230
+ reopen_reason TEXT, reopened_by TEXT, reopened_at INTEGER, -- H5: the only way to rerun a done unit
231
+ created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, done_at INTEGER,
232
+ PRIMARY KEY(workflow_id,unit_id),
233
+ UNIQUE(workflow_id,op_id,subject_key,goal_revision), -- H3/H5: cannot "create a new unit" for the same work
234
+ CHECK(try_budget<=5 OR (budget_raised_by IS NOT NULL AND budget_raised_ref IS NOT NULL))) STRICT;
235
+ CREATE INDEX IF NOT EXISTS ix_units_state ON work_units(workflow_id,state);
236
+ CREATE INDEX IF NOT EXISTS ix_units_op ON work_units(workflow_id,op_id);
237
+ CREATE UNIQUE INDEX IF NOT EXISTS ux_units_cut ON work_units(workflow_id,cut_id,cut_ordinal,goal_revision) WHERE cut_id IS NOT NULL;
238
+ -- A done unit leaves 'done' only via a reopen with a fresh reason (reopened_at changes) - no shortcut.
239
+ CREATE TRIGGER IF NOT EXISTS work_units_done_guard BEFORE UPDATE OF state ON work_units
240
+ WHEN OLD.state='done' AND NEW.state<>'done' BEGIN
241
+ SELECT RAISE(ABORT,'unit-already-passed: reopen needs reopen_reason, reopened_by and a new reopened_at')
242
+ WHERE NEW.reopen_reason IS NULL OR NEW.reopened_by IS NULL OR NEW.reopened_at IS NULL OR NEW.reopened_at IS OLD.reopened_at;
243
+ END;
244
+
245
+ CREATE TABLE IF NOT EXISTS unit_edges(
246
+ workflow_id TEXT NOT NULL,
247
+ from_unit TEXT NOT NULL,
248
+ to_unit TEXT NOT NULL,
249
+ kind TEXT NOT NULL CHECK(kind IN ('after','seam','dependsOn','peer-wait')),
250
+ source TEXT NOT NULL CHECK(source IN ('plan','graph','kernel-graph-edit','peer-wait','seam')),
251
+ created_at INTEGER NOT NULL,
252
+ PRIMARY KEY(workflow_id,from_unit,to_unit,kind),
253
+ FOREIGN KEY(workflow_id,from_unit) REFERENCES work_units(workflow_id,unit_id) ON DELETE CASCADE,
254
+ FOREIGN KEY(workflow_id,to_unit) REFERENCES work_units(workflow_id,unit_id) ON DELETE CASCADE) STRICT;
255
+ CREATE INDEX IF NOT EXISTS ix_unit_edges_to ON unit_edges(workflow_id,to_unit);
256
+
257
+ -- ---------------------------------------------------------------------------------------------------------
258
+ -- A4. Job (try), attempt (dispatch), contract, lease, idempotency
259
+ -- ---------------------------------------------------------------------------------------------------------
260
+ CREATE TABLE IF NOT EXISTS jobs(
261
+ job_id TEXT PRIMARY KEY, -- op-<op>-<token10> | kernel-<wf>
262
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
263
+ unit_id TEXT, -- NULL for kernel jobs
264
+ op_id TEXT,
265
+ try_no INTEGER NOT NULL CHECK(try_no>=1), -- which try OF THE UNIT
266
+ retry_of TEXT REFERENCES jobs(job_id), -- previous job in the family (a retry may change shape)
267
+ resume_of TEXT REFERENCES jobs(job_id),
268
+ retry_class TEXT CHECK(retry_class IS NULL OR retry_class IN ('business','infra','resume','follow-up')),
269
+ generation INTEGER NOT NULL,
270
+ kind TEXT NOT NULL CHECK(kind IN ('op','kernel')),
271
+ role TEXT,
272
+ status TEXT NOT NULL CHECK(status IN ('queued','ready','leased','running','answering','reported','deciding',
273
+ 'effect_unknown','awaiting_owner','succeeded','failed','cancelled')),
274
+ priority_json TEXT CHECK(priority_json IS NULL OR json_valid(priority_json)),
275
+ lease_token TEXT,
276
+ worker_id TEXT,
277
+ deadline INTEGER,
278
+ payload_json TEXT CHECK(payload_json IS NULL OR json_valid(payload_json)), -- input ONLY: records, owned_paths, params, goal_binding, cut, after
279
+ created_at INTEGER NOT NULL,
280
+ updated_at INTEGER NOT NULL,
281
+ FOREIGN KEY(workflow_id,unit_id) REFERENCES work_units(workflow_id,unit_id)) STRICT;
282
+ CREATE INDEX IF NOT EXISTS jobs_queue ON jobs(status,kind,created_at,job_id);
283
+ CREATE INDEX IF NOT EXISTS jobs_op ON jobs(workflow_id,op_id);
284
+ CREATE UNIQUE INDEX IF NOT EXISTS ux_jobs_unit_try ON jobs(workflow_id,unit_id,try_no) WHERE unit_id IS NOT NULL;
285
+
286
+ -- Enqueue gate (H3, H4, H5, H9): every op job needs a unit; unit not done; budget left; valid lineage; workflow accepts work.
287
+ CREATE TRIGGER IF NOT EXISTS jobs_enqueue_guard BEFORE INSERT ON jobs WHEN NEW.kind='op' BEGIN
288
+ SELECT RAISE(ABORT,'workflow-not-accepting-work')
289
+ WHERE (SELECT phase FROM workflows WHERE workflow_id=NEW.workflow_id) NOT IN ('queued','running');
290
+ SELECT RAISE(ABORT,'unit-required') WHERE NEW.unit_id IS NULL;
291
+ SELECT RAISE(ABORT,'unit-already-passed')
292
+ WHERE EXISTS(SELECT 1 FROM work_units u WHERE u.workflow_id=NEW.workflow_id AND u.unit_id=NEW.unit_id AND u.state='done');
293
+ -- A try that ended awaiting_owner asked a question and did not fail: it spends no try of the budget.
294
+ SELECT RAISE(ABORT,'unit-try-budget-exhausted')
295
+ WHERE NEW.try_no - (SELECT count(*) FROM jobs w WHERE w.workflow_id=NEW.workflow_id AND w.unit_id=NEW.unit_id AND w.status='awaiting_owner')
296
+ > (SELECT try_budget FROM work_units u WHERE u.workflow_id=NEW.workflow_id AND u.unit_id=NEW.unit_id);
297
+ -- H5: the try after a reopen follows a PASSED try, so it has no retry_of/resume_of; it is admitted only as the
298
+ -- next try of a unit reopenUnit just moved out of 'done' (reopened_at set, state no longer done).
299
+ SELECT RAISE(ABORT,'first-try-must-be-1-without-lineage')
300
+ WHERE NEW.retry_of IS NULL AND NEW.resume_of IS NULL AND NEW.try_no<>1
301
+ AND NOT EXISTS(SELECT 1 FROM work_units u WHERE u.workflow_id=NEW.workflow_id AND u.unit_id=NEW.unit_id
302
+ AND u.reopened_at IS NOT NULL AND u.state<>'done' AND NEW.try_no=u.tries+1);
303
+ SELECT RAISE(ABORT,'retry-lineage-invalid: retry_of must be a FAILED or awaiting_owner job of the same unit with try_no-1')
304
+ WHERE NEW.retry_of IS NOT NULL AND NOT EXISTS(SELECT 1 FROM jobs p WHERE p.job_id=NEW.retry_of
305
+ AND p.workflow_id=NEW.workflow_id AND p.unit_id=NEW.unit_id AND p.status IN ('failed','awaiting_owner') AND p.try_no=NEW.try_no-1);
306
+ SELECT RAISE(ABORT,'resume-lineage-invalid: resume_of must be a failed/cancelled job of the same unit')
307
+ WHERE NEW.resume_of IS NOT NULL AND NOT EXISTS(SELECT 1 FROM jobs p WHERE p.job_id=NEW.resume_of
308
+ AND p.workflow_id=NEW.workflow_id AND p.unit_id=NEW.unit_id AND p.status IN ('failed','awaiting_owner','cancelled'));
309
+ END;
310
+ -- Job state machine (H9): only pairs in job_transitions; cancelled/succeeded/failed are terminal.
311
+ CREATE TRIGGER IF NOT EXISTS jobs_status_guard BEFORE UPDATE OF status ON jobs
312
+ WHEN NEW.status<>OLD.status AND NOT EXISTS(SELECT 1 FROM job_transitions t WHERE t.from_status=OLD.status AND t.to_status=NEW.status) BEGIN
313
+ SELECT RAISE(ABORT,'job-transition-refused');
314
+ END;
315
+ -- When a job ends, its leases vanish in the same transaction (H12: no leaked leases).
316
+ CREATE TRIGGER IF NOT EXISTS jobs_release_leases AFTER UPDATE OF status ON jobs
317
+ WHEN NEW.status IN ('succeeded','failed','awaiting_owner','cancelled') BEGIN
318
+ DELETE FROM leases WHERE job_id=NEW.job_id;
319
+ END;
320
+
321
+ -- op_attempts: ONE ROW PER DISPATCH (requeue after worker death -> dispatch_seq+1, same job).
322
+ CREATE TABLE IF NOT EXISTS op_attempts(
323
+ attempt_id INTEGER PRIMARY KEY AUTOINCREMENT,
324
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
325
+ job_id TEXT NOT NULL REFERENCES jobs(job_id) ON DELETE CASCADE,
326
+ unit_id TEXT,
327
+ op_id TEXT NOT NULL,
328
+ try_no INTEGER NOT NULL, -- = jobs.try_no
329
+ dispatch_seq INTEGER NOT NULL DEFAULT 1 CHECK(dispatch_seq>=1),
330
+ dispatch_id TEXT NOT NULL, -- ctx_... ; STARCI_DISPATCH_ID
331
+ -- correlation
332
+ span_id TEXT NOT NULL CHECK(length(span_id)=16),
333
+ parent_span_id TEXT, -- span of the decision / Kernel turn that dispatched
334
+ -- who ran
335
+ agent TEXT CHECK(agent IS NULL OR agent IN ('devin','codex','claude')),
336
+ provider TEXT,
337
+ model TEXT, -- attested model id (gen_ai.response.model)
338
+ request_model TEXT, -- requested model (gen_ai.request.model)
339
+ model_profile TEXT, pool TEXT, effort TEXT, difficulty TEXT,
340
+ routed_by TEXT CHECK(routed_by IS NULL OR routed_by IN ('route','config','flag','kernel-override')),
341
+ route_chain_json TEXT CHECK(route_chain_json IS NULL OR json_valid(route_chain_json)),
342
+ route_rejected_json TEXT CHECK(route_rejected_json IS NULL OR json_valid(route_rejected_json)),
343
+ host TEXT NOT NULL DEFAULT 'orca',
344
+ run_id TEXT, task_id TEXT, terminal_handle TEXT, worker_pid INTEGER,
345
+ managed INTEGER NOT NULL DEFAULT 0 CHECK(managed IN (0,1)),
346
+ scratch_dir TEXT, -- STARCI_JOB_SCRATCH
347
+ -- replay (Bazel action key): enough to rebuild the exact try
348
+ runtime_rev TEXT, -- commit .claude
349
+ cli_name TEXT, cli_version TEXT, -- devin 2.x, codex 0.x ...
350
+ contract_sha TEXT, -- digest contracts.markdown
351
+ config_sha TEXT, -- digest of the effective config.yaml
352
+ prompt_sha TEXT REFERENCES blobs(sha256), -- the real prompt sent to the terminal
353
+ transcript_sha TEXT REFERENCES blobs(sha256), -- the WHOLE terminal scrollback at end, redacted (UI-API sec. 2.10);
354
+ -- while running: attempt_transcript_snapshots (every 60 seconds)
355
+ session_sha TEXT REFERENCES blobs(sha256), -- the CLI session file (~/.claude/projects/*.jsonl, ~/.codex/sessions/...), redacted
356
+ -- where
357
+ repo_root TEXT, worktree_path TEXT, branch TEXT, wf_branch TEXT,
358
+ base_sha TEXT, head_sha TEXT, integrated_sha TEXT,
359
+ -- when
360
+ routed_at INTEGER, dispatched_at INTEGER, started_at INTEGER, attested_at INTEGER, reported_at INTEGER,
361
+ consumed_at INTEGER, checked_at INTEGER, settled_at INTEGER, released_at INTEGER,
362
+ terminal_closed_at INTEGER, task_closed_at INTEGER, worktree_removed_at INTEGER,
363
+ wall_ms INTEGER, -- dispatch -> settle, monotonic
364
+ -- outcome: the op's word and the verdict are TWO fields
365
+ report_outcome TEXT CHECK(report_outcome IS NULL OR report_outcome IN ('done','partial','failed','ask','blocked')),
366
+ verdict TEXT CHECK(verdict IS NULL OR verdict IN ('pass','fail','partial','blocked','dropped','cancelled')),
367
+ settled_by TEXT CHECK(settled_by IS NULL OR settled_by IN ('settler','kernel','supervisor','reconcile')),
368
+ decision_id TEXT,
369
+ failure_class TEXT, effect_state TEXT, next_step TEXT,
370
+ settle_json TEXT CHECK(settle_json IS NULL OR json_valid(settle_json)),
371
+ end_state TEXT CHECK(end_state IS NULL OR end_state IN ('settled','worker-dead','requeued','cancelled','effect-unknown')),
372
+ -- summarized cost (detail in llm_usage); NULL when unmeasurable
373
+ tokens_in INTEGER, tokens_out INTEGER, cost_usd REAL,
374
+ usage_source TEXT CHECK(usage_source IS NULL OR usage_source IN ('cli-transcript','provider-report','unavailable')),
375
+ usage_reason TEXT, -- why usage is 'unavailable' (the agent has no usage adapter, or no session file was found)
376
+ -- the owner-facing reason an attempt failed, was blocked, refused or is waiting (starci/why@1, scripts/kernel/why.mjs, docs/why.md), written with the settle
377
+ why_json TEXT CHECK(why_json IS NULL OR json_valid(why_json)),
378
+ UNIQUE(workflow_id,dispatch_id),
379
+ UNIQUE(job_id,dispatch_seq)) STRICT;
380
+ CREATE INDEX IF NOT EXISTS ix_attempts_unit ON op_attempts(workflow_id,unit_id,attempt_id);
381
+ CREATE INDEX IF NOT EXISTS ix_attempts_op ON op_attempts(workflow_id,op_id);
382
+ CREATE INDEX IF NOT EXISTS ix_attempts_time ON op_attempts(workflow_id,dispatched_at);
383
+ CREATE INDEX IF NOT EXISTS ix_attempts_model ON op_attempts(agent,model,op_id);
384
+ CREATE INDEX IF NOT EXISTS ix_attempts_terminal ON op_attempts(terminal_handle);
385
+ CREATE INDEX IF NOT EXISTS ix_attempts_open ON op_attempts(settled_at,end_state);
386
+ CREATE INDEX IF NOT EXISTS ix_attempts_head ON op_attempts(head_sha);
387
+ CREATE INDEX IF NOT EXISTS ix_attempts_reported ON op_attempts(reported_at) WHERE settled_at IS NULL;
388
+ -- Dispatch gate (H9): an attempt row may only be created when the job just went ready->leased (job_transitions
389
+ -- only allows leased from ready) and the workflow is running. A cancelled/archived job cannot be dispatched.
390
+ CREATE TRIGGER IF NOT EXISTS op_attempts_dispatch_guard BEFORE INSERT ON op_attempts BEGIN
391
+ SELECT RAISE(ABORT,'dispatch-requires-ready-then-leased')
392
+ WHERE (SELECT status FROM jobs WHERE job_id=NEW.job_id) IS NOT 'leased';
393
+ SELECT RAISE(ABORT,'dispatch-requires-running-workflow')
394
+ WHERE (SELECT phase FROM workflows WHERE workflow_id=NEW.workflow_id) IS NOT 'running';
395
+ SELECT RAISE(ABORT,'dispatch-previous-attempt-open')
396
+ WHERE EXISTS(SELECT 1 FROM op_attempts a WHERE a.job_id=NEW.job_id AND a.end_state IS NULL AND a.settled_at IS NULL);
397
+ END;
398
+
399
+ -- contracts: kernel -> op. Keyed by attempt_id: a re-dispatch does NOT overwrite the previous contract.
400
+ CREATE TABLE IF NOT EXISTS contracts(
401
+ attempt_id INTEGER PRIMARY KEY REFERENCES op_attempts(attempt_id) ON DELETE CASCADE,
402
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
403
+ job_id TEXT NOT NULL,
404
+ contract_rev TEXT, -- admitted contract version (modules/kernel/contract-changes)
405
+ markdown TEXT NOT NULL,
406
+ context_json TEXT CHECK(context_json IS NULL OR json_valid(context_json)),
407
+ created_at INTEGER NOT NULL) STRICT;
408
+
409
+ -- resources / leases: fencing along the write path. A missing resources row = capacity 1 (not pre-seeded).
410
+ CREATE TABLE IF NOT EXISTS resources(
411
+ resource_key TEXT PRIMARY KEY,
412
+ capacity INTEGER NOT NULL CHECK(capacity>=0),
413
+ declared_by TEXT, declared_at INTEGER) STRICT;
414
+ CREATE TABLE IF NOT EXISTS leases(
415
+ resource_key TEXT NOT NULL,
416
+ job_id TEXT NOT NULL REFERENCES jobs(job_id) ON DELETE CASCADE,
417
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
418
+ op_id TEXT, try_no INTEGER NOT NULL, generation INTEGER NOT NULL,
419
+ token TEXT NOT NULL, -- = jobs.lease_token
420
+ units INTEGER NOT NULL CHECK(units>0),
421
+ attempt_id INTEGER, -- owner: the dispatch holding it (NULL between lease and dispatch)
422
+ holder TEXT, -- holding terminal handle / pid - GC can ask "who holds it, still alive"
423
+ acquired_at INTEGER NOT NULL,
424
+ renewed_at INTEGER,
425
+ expires_at INTEGER NOT NULL CHECK(expires_at>acquired_at), -- mandatory TTL; expiry -> dead-worker flow (H12)
426
+ PRIMARY KEY(resource_key,job_id)) STRICT;
427
+ CREATE INDEX IF NOT EXISTS leases_expiry ON leases(expires_at);
428
+ CREATE TRIGGER IF NOT EXISTS leases_match_job BEFORE INSERT ON leases BEGIN
429
+ SELECT RAISE(ABORT,'lease-identity-drift') WHERE NOT EXISTS(
430
+ SELECT 1 FROM jobs j WHERE j.job_id=NEW.job_id AND j.workflow_id=NEW.workflow_id AND j.op_id IS NEW.op_id
431
+ AND j.try_no=NEW.try_no AND j.generation=NEW.generation AND j.lease_token=NEW.token);
432
+ END;
433
+ CREATE TRIGGER IF NOT EXISTS leases_match_job_update BEFORE UPDATE ON leases BEGIN
434
+ SELECT RAISE(ABORT,'lease-identity-drift') WHERE NOT EXISTS(
435
+ SELECT 1 FROM jobs j WHERE j.job_id=NEW.job_id AND j.workflow_id=NEW.workflow_id AND j.op_id IS NEW.op_id
436
+ AND j.try_no=NEW.try_no AND j.generation=NEW.generation AND j.lease_token=NEW.token);
437
+ END;
438
+
439
+ -- api_requests: idempotency for every write verb that may be re-invoked (report, settle, decide, enqueue, drop, decisions --resolve).
440
+ -- request_id = --request-id or sha(verb + dispatch_id + args). A repeat call returns the old result. The harness does NOT write (UI reads only).
441
+ CREATE TABLE IF NOT EXISTS api_requests(
442
+ request_id TEXT PRIMARY KEY,
443
+ verb TEXT NOT NULL,
444
+ caller TEXT, -- kernel:<wf> | op:<attempt_id> | settler | reconciler | supervisor | owner-cli
445
+ workflow_id TEXT REFERENCES workflows(workflow_id) ON DELETE CASCADE,
446
+ attempt_id INTEGER,
447
+ args_sha TEXT NOT NULL,
448
+ status TEXT NOT NULL CHECK(status IN ('running','done','failed')),
449
+ result_json TEXT CHECK(result_json IS NULL OR json_valid(result_json)),
450
+ created_at INTEGER NOT NULL, finished_at INTEGER) STRICT;
451
+ CREATE INDEX IF NOT EXISTS ix_api_requests_attempt ON api_requests(attempt_id);
452
+
453
+ -- ---------------------------------------------------------------------------------------------------------
454
+ -- A5. Reports, checks, settle, lands, artifacts, evidence, LLM cost
455
+ -- ---------------------------------------------------------------------------------------------------------
456
+ CREATE TABLE IF NOT EXISTS reports(
457
+ report_id INTEGER PRIMARY KEY AUTOINCREMENT,
458
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
459
+ attempt_id INTEGER NOT NULL UNIQUE REFERENCES op_attempts(attempt_id) ON DELETE CASCADE,
460
+ dispatch_id TEXT NOT NULL,
461
+ job_id TEXT NOT NULL,
462
+ outcome TEXT NOT NULL CHECK(outcome IN ('done','partial','failed','ask','blocked')),
463
+ report_json TEXT NOT NULL CHECK(json_valid(report_json)), -- starci/op-report@1, the ONLY version
464
+ summary TEXT GENERATED ALWAYS AS (json_extract(report_json,'$.summary')) VIRTUAL,
465
+ from_terminal TEXT,
466
+ consumed_at INTEGER,
467
+ created_at INTEGER NOT NULL,
468
+ UNIQUE(workflow_id,dispatch_id)) STRICT;
469
+ -- Reports are immutable (H10): resubmit identical content -> api_requests returns the old result; different content -> refused.
470
+ CREATE TRIGGER IF NOT EXISTS reports_immutable BEFORE UPDATE OF report_json, outcome, attempt_id, dispatch_id ON reports BEGIN
471
+ SELECT RAISE(ABORT,'report-already-filed: reports are immutable');
472
+ END;
473
+
474
+ -- check_runs: each run of one check of one try. run_seq = which rerun of the same check.
475
+ CREATE TABLE IF NOT EXISTS check_runs(
476
+ check_id INTEGER PRIMARY KEY AUTOINCREMENT,
477
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
478
+ attempt_id INTEGER NOT NULL REFERENCES op_attempts(attempt_id) ON DELETE CASCADE,
479
+ job_id TEXT NOT NULL,
480
+ op_id TEXT NOT NULL,
481
+ span_id TEXT NOT NULL CHECK(length(span_id)=16),
482
+ parent_span_id TEXT, -- = op_attempts.span_id
483
+ name TEXT NOT NULL, -- canon-scan, check-scoped-lint, tsc-app, canon-parity, lint-check ...
484
+ phase TEXT NOT NULL CHECK(phase IN ('before','after','verify','parity','integrate')),
485
+ runner TEXT NOT NULL CHECK(runner IN ('op','settler','kernel','parity','integrate')),
486
+ run_seq INTEGER NOT NULL DEFAULT 1,
487
+ command TEXT, -- never holds secrets
488
+ cwd TEXT,
489
+ input_digest TEXT, -- base sha + owned paths + tool rev: the reuse key (replaces settle-parity/*.json)
490
+ authority TEXT NOT NULL CHECK(authority IN ('runtime','declared')), -- H8: 'runtime' = the canonical command owned
491
+ -- and run by the runtime; 'declared' = self-reported by the op, evidence only, does not count toward the verdict
492
+ exit_code INTEGER, -- the RAW exit code the runner observed (never coerced to 0)
493
+ declared_exit_code INTEGER, -- the exit code the op SELF-DECLARED in its report (may differ from exit_code - that is a signal)
494
+ attribution_json TEXT CHECK(attribution_json IS NULL OR json_valid(attribution_json)), -- structured record of the error portion outside the slice
495
+ status TEXT NOT NULL CHECK(status IN ('pass','fail','unavailable','error','skipped')),
496
+ started_at INTEGER, finished_at INTEGER, wall_ms INTEGER,
497
+ stdout_sha TEXT REFERENCES blobs(sha256),
498
+ stderr_sha TEXT REFERENCES blobs(sha256),
499
+ output_sha TEXT REFERENCES blobs(sha256), -- parsed JSON (canon-scan --json ...)
500
+ summary_json TEXT CHECK(summary_json IS NULL OR json_valid(summary_json)),
501
+ note TEXT,
502
+ created_at INTEGER NOT NULL,
503
+ UNIQUE(attempt_id,runner,phase,name,run_seq),
504
+ CHECK(NOT (status='pass' AND exit_code IS NOT NULL AND exit_code<>0)), -- H8: no "pass" with a raw exit != 0
505
+ CHECK(runner<>'op' OR authority='declared')) STRICT; -- an op-run check is always evidence only
506
+ CREATE INDEX IF NOT EXISTS ix_checks_name ON check_runs(workflow_id,name,status);
507
+ CREATE INDEX IF NOT EXISTS ix_checks_cache ON check_runs(name,input_digest);
508
+
509
+ -- settle_tails: the queue of tail work after settle (replaces <ledger dir>/settle-tail/<job>.json).
510
+ CREATE TABLE IF NOT EXISTS settle_tails(
511
+ attempt_id INTEGER PRIMARY KEY REFERENCES op_attempts(attempt_id) ON DELETE CASCADE,
512
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
513
+ state TEXT NOT NULL CHECK(state IN ('queued','running','done','failed')),
514
+ tries INTEGER NOT NULL DEFAULT 0,
515
+ due_at INTEGER, last_error TEXT,
516
+ queued_at INTEGER NOT NULL, started_at INTEGER, done_at INTEGER) STRICT;
517
+
518
+ -- product_lands: api product-land (merge wf/<wf> into the product repo main; separate from settle).
519
+ CREATE TABLE IF NOT EXISTS product_lands(
520
+ land_id INTEGER PRIMARY KEY AUTOINCREMENT,
521
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
522
+ span_id TEXT NOT NULL CHECK(length(span_id)=16),
523
+ repo_root TEXT NOT NULL,
524
+ wf_branch TEXT NOT NULL,
525
+ main_before TEXT, merged_sha TEXT,
526
+ result TEXT NOT NULL CHECK(result IN ('queued','landed','failed','conflict','red','busy','main-moving')),
527
+ reason TEXT,
528
+ checks_json TEXT CHECK(checks_json IS NULL OR json_valid(checks_json)), -- land checks + import scan (summary)
529
+ output_sha TEXT REFERENCES blobs(sha256),
530
+ pushed INTEGER NOT NULL DEFAULT 0 CHECK(pushed IN (0,1)),
531
+ started_at INTEGER NOT NULL, finished_at INTEGER) STRICT;
532
+ CREATE INDEX IF NOT EXISTS ix_lands_wf ON product_lands(workflow_id,started_at);
533
+
534
+ -- job_artifacts: every output file, bytes in the blob store. Kernel artifacts (scan, dispatch-ready) have no attempt.
535
+ CREATE TABLE IF NOT EXISTS job_artifacts(
536
+ artifact_id INTEGER PRIMARY KEY AUTOINCREMENT,
537
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
538
+ attempt_id INTEGER REFERENCES op_attempts(attempt_id) ON DELETE CASCADE,
539
+ job_id TEXT,
540
+ op_id TEXT,
541
+ cut TEXT,
542
+ role TEXT NOT NULL CHECK(role IN (
543
+ 'check-output','check-stdout','check-stderr','patch','diff','report-attachment','log',
544
+ 'direction','prompt','render','redline','critique',
545
+ 'capture','dom','screenshot','video','trace','uat-run','metrics','salvage','scan','other')),
546
+ kind TEXT NOT NULL CHECK(kind IN ('diff','patch','image','video','report','log','trace','file')),
547
+ subkind TEXT CHECK(subkind IS NULL OR subkind IN ('draw-render','asset-gen','app-capture','e2e-capture','uat-capture',
548
+ 'uat-video','e2e-video','playwright-trace','patch','patch-json','diff','report','log','critique','metrics',
549
+ 'grammar-proposal','asset-request','terminal-transcript','cli-transcript')),
550
+ name TEXT NOT NULL, -- logical name: 'checks/2-canon-scan/output', 'patch.diff', 'rounds/2/desktop-light.png'
551
+ sha256 TEXT NOT NULL REFERENCES blobs(sha256),
552
+ bytes INTEGER NOT NULL,
553
+ media_type TEXT NOT NULL,
554
+ label TEXT, -- XBase#state@viewport ...
555
+ scope_ref TEXT, -- id of the Work record the artifact is about
556
+ round INTEGER, -- draw/audit round
557
+ run_id TEXT, -- runId of uat.verify
558
+ origin TEXT NOT NULL CHECK(origin IN ('op','settler','checker','kernel')),
559
+ base_sha TEXT, head_sha TEXT, integrated_sha TEXT,
560
+ created_at INTEGER NOT NULL,
561
+ CHECK(attempt_id IS NOT NULL OR origin='kernel')) STRICT;
562
+ -- Artifacts are immutable (H10): key (attempt_id, name), bytes keyed by sha; no UPDATE of content/name, no sha overwrite.
563
+ CREATE TRIGGER IF NOT EXISTS job_artifacts_immutable BEFORE UPDATE OF sha256, bytes, name, attempt_id, media_type ON job_artifacts BEGIN
564
+ SELECT RAISE(ABORT,'artifacts are immutable: file a new name');
565
+ END;
566
+
567
+ -- attempt_transcript_snapshots (UI-API sec. 2.10): periodic (60 s) redacted scrollback snapshots of a running op terminal.
568
+ -- No new row when the scrollback is unchanged (UNIQUE attempt+sha). The final one is op_attempts.transcript_sha.
569
+ CREATE TABLE IF NOT EXISTS attempt_transcript_snapshots(
570
+ snapshot_id INTEGER PRIMARY KEY AUTOINCREMENT,
571
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
572
+ attempt_id INTEGER NOT NULL REFERENCES op_attempts(attempt_id) ON DELETE CASCADE,
573
+ at INTEGER NOT NULL,
574
+ lines INTEGER NOT NULL,
575
+ bytes INTEGER NOT NULL,
576
+ sha256 TEXT NOT NULL REFERENCES blobs(sha256),
577
+ UNIQUE(attempt_id,sha256)) STRICT;
578
+ CREATE INDEX IF NOT EXISTS ix_transcript_snap ON attempt_transcript_snapshots(attempt_id,at);
579
+ CREATE UNIQUE INDEX IF NOT EXISTS ux_artifacts_attempt_name ON job_artifacts(attempt_id,name) WHERE attempt_id IS NOT NULL;
580
+ CREATE UNIQUE INDEX IF NOT EXISTS ux_artifacts_kernel_name ON job_artifacts(workflow_id,name) WHERE attempt_id IS NULL;
581
+ CREATE INDEX IF NOT EXISTS ix_artifacts_blob ON job_artifacts(sha256);
582
+ CREATE INDEX IF NOT EXISTS ix_artifacts_role ON job_artifacts(workflow_id,role);
583
+ CREATE INDEX IF NOT EXISTS ix_artifacts_scope ON job_artifacts(scope_ref);
584
+
585
+ CREATE TABLE IF NOT EXISTS report_attachments(
586
+ report_id INTEGER NOT NULL REFERENCES reports(report_id) ON DELETE CASCADE,
587
+ artifact_id INTEGER NOT NULL REFERENCES job_artifacts(artifact_id) ON DELETE CASCADE,
588
+ PRIMARY KEY(report_id,artifact_id)) STRICT;
589
+
590
+ CREATE TABLE IF NOT EXISTS artifact_proofs(
591
+ artifact_id INTEGER PRIMARY KEY REFERENCES job_artifacts(artifact_id) ON DELETE CASCADE,
592
+ claims_json TEXT NOT NULL CHECK(json_valid(claims_json)), -- {frs[], cases[], shapes[], specs[]}
593
+ code_sha TEXT,
594
+ deps_json TEXT NOT NULL CHECK(json_valid(deps_json)), -- [{path, kind: code|work, digest}]
595
+ created_at INTEGER NOT NULL) STRICT;
596
+
597
+ -- work_citations: which artifact/blob a Work (git) record cites - pins the blob, renders the record's images, detects broken citations.
598
+ CREATE TABLE IF NOT EXISTS work_citations(
599
+ record_id TEXT NOT NULL, -- uat.<feature>.<flow>, ui.<feature>.<screen>, impl.<...>, brand
600
+ record_path TEXT NOT NULL, -- features/<f>/<family>/<name>/evidence.yaml
601
+ field TEXT NOT NULL, -- 'run.screens[0]', 'assets[3]'
602
+ artifact_id INTEGER REFERENCES job_artifacts(artifact_id) ON DELETE RESTRICT,
603
+ sha256 TEXT NOT NULL REFERENCES blobs(sha256) ON DELETE RESTRICT,
604
+ role TEXT CHECK(role IS NULL OR role IN ('direction','capture','uat-screen','uat-video','uat-result','render','layout-capture')),
605
+ record_rev TEXT,
606
+ created_at INTEGER NOT NULL,
607
+ PRIMARY KEY(record_id,field)) STRICT;
608
+ CREATE INDEX IF NOT EXISTS ix_citations_blob ON work_citations(sha256);
609
+ CREATE INDEX IF NOT EXISTS ix_citations_path ON work_citations(record_path);
610
+
611
+ -- interface_audits: scope + verdict of interface.audit (replaces features/<f>/operations/<name>/index.yaml + E/**).
612
+ CREATE TABLE IF NOT EXISTS interface_audits(
613
+ audit_id TEXT PRIMARY KEY, -- operation.<feature>.<name>
614
+ workflow_id TEXT REFERENCES workflows(workflow_id) ON DELETE CASCADE,
615
+ attempt_id INTEGER REFERENCES op_attempts(attempt_id) ON DELETE SET NULL,
616
+ feature TEXT NOT NULL,
617
+ scope_json TEXT NOT NULL CHECK(json_valid(scope_json)),
618
+ verdict TEXT CHECK(verdict IS NULL OR verdict IN ('pass','fail','partial')),
619
+ findings_json TEXT CHECK(findings_json IS NULL OR json_valid(findings_json)),
620
+ route_to TEXT CHECK(route_to IS NULL OR route_to IN ('interface.implement','interface.draw')),
621
+ created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL) STRICT;
622
+
623
+ -- llm_usage: LLM cost per OTel GenAI semconv - one row per (attempt | Kernel turn) x model.
624
+ -- Supervisor and [Worker] cost lives in machine.llm_usage.
625
+ CREATE TABLE IF NOT EXISTS llm_usage(
626
+ usage_id INTEGER PRIMARY KEY AUTOINCREMENT,
627
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
628
+ subject_type TEXT NOT NULL CHECK(subject_type IN ('attempt','kernel-turn')),
629
+ attempt_id INTEGER REFERENCES op_attempts(attempt_id) ON DELETE CASCADE, -- when subject_type='attempt'
630
+ turn_ref TEXT, -- when 'kernel-turn': '<seat>:<n>' or a decision_id
631
+ span_id TEXT,
632
+ provider TEXT NOT NULL, -- gen_ai.provider.name
633
+ request_model TEXT, response_model TEXT, -- gen_ai.request.model / gen_ai.response.model
634
+ input_tokens INTEGER, output_tokens INTEGER, cache_read_tokens INTEGER, cache_write_tokens INTEGER,
635
+ reasoning_tokens INTEGER,
636
+ cost_usd REAL,
637
+ turns INTEGER, tool_calls INTEGER, tool_errors INTEGER,
638
+ source TEXT NOT NULL CHECK(source IN ('cli-transcript','provider-report')),
639
+ at INTEGER NOT NULL,
640
+ CHECK((subject_type='attempt' AND attempt_id IS NOT NULL) OR (subject_type='kernel-turn' AND turn_ref IS NOT NULL))) STRICT;
641
+ CREATE INDEX IF NOT EXISTS ix_usage_attempt ON llm_usage(attempt_id);
642
+ CREATE INDEX IF NOT EXISTS ix_usage_wf ON llm_usage(workflow_id,subject_type,at);
643
+
644
+ -- ---------------------------------------------------------------------------------------------------------
645
+ -- A6. Inbox, decision items, decisions, conditions, incidents, shared foundations, signals
646
+ -- ---------------------------------------------------------------------------------------------------------
647
+ CREATE TABLE IF NOT EXISTS inbox(
648
+ inbox_id INTEGER PRIMARY KEY AUTOINCREMENT,
649
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
650
+ kind TEXT NOT NULL CHECK(kind IN ('goal','goal-revision','peer-message','worker-question','owner-answer','amendment')),
651
+ key TEXT,
652
+ from_ref TEXT, -- owner | wf-<peer> | attempt:<id>
653
+ attempt_id INTEGER, -- a worker's question: which attempt asked
654
+ payload_json TEXT NOT NULL CHECK(json_valid(payload_json)),
655
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','claimed','applied','rejected','done','superseded')),
656
+ disposition_json TEXT CHECK(disposition_json IS NULL OR json_valid(disposition_json)),
657
+ created_at INTEGER NOT NULL,
658
+ applied_at INTEGER) STRICT;
659
+ CREATE INDEX IF NOT EXISTS ix_inbox_open ON inbox(workflow_id,kind,status);
660
+
661
+ CREATE TABLE IF NOT EXISTS decision_items(
662
+ di_id TEXT PRIMARY KEY, -- di-<sha8>
663
+ -- MB-07: key = '<kind>:<entity>:<error signature>:<head|rev>' - EVERY part mandatory, no empty part. Signature
664
+ -- changes -> new DI and the old DI superseded (superseded_by), never merged forever into the old DI.
665
+ idempotency_key TEXT NOT NULL UNIQUE CHECK(idempotency_key NOT LIKE '%::%' AND idempotency_key NOT LIKE '%:'
666
+ AND idempotency_key NOT LIKE ':%' AND length(idempotency_key)>=5),
667
+ key_parts_json TEXT NOT NULL CHECK(json_valid(key_parts_json) AND json_type(key_parts_json)='object'),
668
+ superseded_by TEXT REFERENCES decision_items(di_id),
669
+ workflow_id TEXT REFERENCES workflows(workflow_id) ON DELETE CASCADE,
670
+ kind TEXT NOT NULL, -- settle-nongreen | worker-question | progress-stall | stale-gate | ...
671
+ decider TEXT NOT NULL CHECK(decider IN ('kernel','supervisor','owner')),
672
+ entity_type TEXT CHECK(entity_type IS NULL OR entity_type IN ('job','unit','attempt','workflow','lane','service','seat')),
673
+ entity_id TEXT,
674
+ job_id TEXT, unit_id TEXT, attempt_id INTEGER,
675
+ summary TEXT NOT NULL,
676
+ status TEXT NOT NULL CHECK(status IN ('open','claimed','resolved','escalated','expired','superseded')),
677
+ opened_by TEXT NOT NULL,
678
+ opened_at INTEGER NOT NULL,
679
+ due_at INTEGER,
680
+ escalate_to TEXT, escalations INTEGER NOT NULL DEFAULT 0,
681
+ claim_by TEXT, claim_at INTEGER, claim_ttl_ms INTEGER,
682
+ resolved_by TEXT, resolved_at INTEGER, resolution_verb TEXT, decision_id TEXT,
683
+ evidence_json TEXT CHECK(evidence_json IS NULL OR json_valid(evidence_json)),
684
+ options_json TEXT CHECK(options_json IS NULL OR json_valid(options_json)),
685
+ allowed_verbs_json TEXT CHECK(allowed_verbs_json IS NULL OR json_valid(allowed_verbs_json)),
686
+ payload_json TEXT NOT NULL CHECK(json_valid(payload_json))) STRICT;
687
+ CREATE INDEX IF NOT EXISTS ix_di_open ON decision_items(decider,status,due_at);
688
+ CREATE INDEX IF NOT EXISTS ix_di_wf ON decision_items(workflow_id,status);
689
+ CREATE INDEX IF NOT EXISTS ix_di_ent ON decision_items(entity_type,entity_id);
690
+
691
+ CREATE TABLE IF NOT EXISTS decisions(
692
+ decision_id TEXT PRIMARY KEY,
693
+ workflow_id TEXT REFERENCES workflows(workflow_id) ON DELETE CASCADE,
694
+ span_id TEXT NOT NULL CHECK(length(span_id)=16), -- child span of the Kernel turn; dispatched attempts take it as parent
695
+ parent_span_id TEXT,
696
+ decider TEXT NOT NULL, -- 'kernel:<wf>' | 'supervisor' | 'owner'
697
+ di_id TEXT REFERENCES decision_items(di_id),
698
+ subject_type TEXT CHECK(subject_type IS NULL OR subject_type IN ('job','unit','attempt','graph','workflow')),
699
+ subject_id TEXT,
700
+ choice TEXT NOT NULL,
701
+ rationale TEXT,
702
+ result_json TEXT CHECK(result_json IS NULL OR json_valid(result_json)),
703
+ decided_at INTEGER NOT NULL) STRICT;
704
+ CREATE INDEX IF NOT EXISTS ix_decisions_wf ON decisions(workflow_id,decided_at);
705
+ CREATE INDEX IF NOT EXISTS ix_decisions_subj ON decisions(subject_type,subject_id);
706
+
707
+ -- conditions (K8s metav1.Condition): observed status per facet; "why stuck" = the first condition not yet True.
708
+ -- A controller writes 'Unknown' on first sight of an entity. Each status change -> a 'condition-changed' event.
709
+ CREATE TABLE IF NOT EXISTS conditions(
710
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
711
+ entity_type TEXT NOT NULL CHECK(entity_type IN ('workflow','unit','job','attempt')),
712
+ entity_id TEXT NOT NULL,
713
+ type TEXT NOT NULL, -- Routed, Dispatched, WorkerAlive, Reported, ChecksPassed, Settled, Released, WorktreeClean, GoalObserved
714
+ status TEXT NOT NULL CHECK(status IN ('True','False','Unknown')),
715
+ reason TEXT NOT NULL, -- CamelCase: ProviderCircuitOpen, RamThrottled, NoReportAfterSla, CheckFailed ...
716
+ message TEXT,
717
+ owner TEXT CHECK(owner IS NULL OR owner IN ('owner','supervisor','kernel','controller','op')), -- who must clear it
718
+ observed_generation INTEGER,
719
+ last_transition_at INTEGER NOT NULL,
720
+ updated_at INTEGER NOT NULL,
721
+ PRIMARY KEY(entity_type,entity_id,type)) STRICT;
722
+ CREATE INDEX IF NOT EXISTS ix_conditions_open ON conditions(workflow_id,status);
723
+
724
+ -- incidents: kind is an enum (today hundreds of free-form kinds), with owner and deadline (GC can ask "which incident is overdue, who must clear it").
725
+ CREATE TABLE IF NOT EXISTS incidents(
726
+ incident_id TEXT PRIMARY KEY,
727
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
728
+ op_id TEXT, job_id TEXT, attempt_id INTEGER,
729
+ kind TEXT NOT NULL CHECK(kind IN ('infra-provider','config-defect','owner-ask','credential-missing','safety-block',
730
+ 'runtime-defect','evidence-missing','scope-change','partial-effect','other')),
731
+ detail TEXT,
732
+ owner TEXT NOT NULL CHECK(owner IN ('kernel','supervisor','owner')),
733
+ due_at INTEGER, -- overdue -> DI escalation
734
+ attempts INTEGER NOT NULL DEFAULT 0, model_calls INTEGER NOT NULL DEFAULT 0,
735
+ tokens INTEGER NOT NULL DEFAULT 0, elapsed_ms INTEGER NOT NULL DEFAULT 0,
736
+ last_progress TEXT,
737
+ status TEXT NOT NULL CHECK(status IN ('open','resolved','superseded')),
738
+ resolved_reason TEXT CHECK(resolved_reason IS NULL OR resolved_reason IN ('fixed','answered','workflow-ended','superseded','dropped')),
739
+ created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, resolved_at INTEGER,
740
+ CHECK(status='open' OR (resolved_at IS NOT NULL AND resolved_reason IS NOT NULL))) STRICT;
741
+ CREATE INDEX IF NOT EXISTS ix_incidents_open ON incidents(status,due_at);
742
+ -- When a workflow ends, its incidents close in the same transaction (H12: 405 orphaned incidents).
743
+ CREATE TRIGGER IF NOT EXISTS workflows_close_incidents AFTER UPDATE OF phase ON workflows
744
+ WHEN NEW.phase IN ('finished','archived') BEGIN
745
+ UPDATE incidents SET status='resolved', resolved_reason='workflow-ended', resolved_at=NEW.updated_at, updated_at=NEW.updated_at
746
+ WHERE workflow_id=NEW.workflow_id AND status='open';
747
+ END;
748
+
749
+ CREATE TABLE IF NOT EXISTS foundations(
750
+ name TEXT PRIMARY KEY,
751
+ kind TEXT NOT NULL CHECK(kind IN ('brand','grammar','layout-tree','shell','module','contract','other')),
752
+ state TEXT NOT NULL CHECK(state IN ('unclaimed','claimed','published','retired')),
753
+ owner_workflow TEXT, version TEXT, detail TEXT, work_ref TEXT,
754
+ updated_at INTEGER NOT NULL) STRICT;
755
+ CREATE TABLE IF NOT EXISTS foundation_declarations(
756
+ workflow_id TEXT PRIMARY KEY REFERENCES workflows(workflow_id) ON DELETE CASCADE,
757
+ builds_none INTEGER NOT NULL CHECK(builds_none IN (0,1)), detail TEXT, at INTEGER NOT NULL) STRICT;
758
+ CREATE TABLE IF NOT EXISTS path_transfers(
759
+ path TEXT PRIMARY KEY,
760
+ from_workflow TEXT, to_workflow TEXT, bridge_id TEXT,
761
+ state TEXT NOT NULL CHECK(state IN ('proposed','applied','reverted')),
762
+ detail_json TEXT CHECK(detail_json IS NULL OR json_valid(detail_json)), at INTEGER NOT NULL) STRICT;
763
+ CREATE TABLE IF NOT EXISTS record_changes(
764
+ change_id TEXT PRIMARY KEY,
765
+ workflow_id TEXT REFERENCES workflows(workflow_id) ON DELETE CASCADE,
766
+ record_id TEXT NOT NULL, record_path TEXT, reason TEXT, by TEXT, at INTEGER NOT NULL) STRICT;
767
+
768
+ -- signals: process locks/fences ONLY.
769
+ CREATE TABLE IF NOT EXISTS signals(
770
+ scope TEXT NOT NULL CHECK(scope IN ('kernel','stop','launch','decision-doorbell')),
771
+ key TEXT NOT NULL,
772
+ workflow_id TEXT REFERENCES workflows(workflow_id) ON DELETE CASCADE, -- a signal does not outlive its workflow (data #18)
773
+ holder_pid INTEGER, token TEXT,
774
+ value_json TEXT CHECK(value_json IS NULL OR json_valid(value_json)),
775
+ at INTEGER NOT NULL, expires_at INTEGER,
776
+ PRIMARY KEY(scope,key)) STRICT;
777
+
778
+ -- ---------------------------------------------------------------------------------------------------------
779
+ -- A7. Events and logs (append-only, FTS)
780
+ -- ---------------------------------------------------------------------------------------------------------
781
+ -- events: the audit journal. prev_digest/digest are computed by the JS writer inside BEGIN IMMEDIATE:
782
+ -- digest = sha256(prev_digest || event_id || kind || coalesce(payload_json,'') || created_at)
783
+ CREATE TABLE IF NOT EXISTS events(
784
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
785
+ event_id TEXT NOT NULL UNIQUE,
786
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
787
+ generation INTEGER NOT NULL,
788
+ entity_type TEXT NOT NULL,
789
+ entity_id TEXT NOT NULL,
790
+ attempt_id INTEGER, -- when the event belongs to a try
791
+ span_id TEXT,
792
+ kind TEXT NOT NULL,
793
+ payload_json TEXT CHECK(payload_json IS NULL OR (json_valid(payload_json) AND length(payload_json)<=16384)),
794
+ payload_sha TEXT REFERENCES blobs(sha256), -- payload larger than 16 KiB
795
+ prev_digest TEXT,
796
+ digest TEXT NOT NULL CHECK(length(digest)=64),
797
+ occurred_at INTEGER NOT NULL, -- when it happened (data #12: split from write time)
798
+ created_at INTEGER NOT NULL) STRICT; -- when written (recorded_at)
799
+ CREATE INDEX IF NOT EXISTS events_entity ON events(workflow_id,entity_type,entity_id,seq);
800
+ CREATE INDEX IF NOT EXISTS events_kind ON events(workflow_id,kind,seq);
801
+ CREATE INDEX IF NOT EXISTS events_workflow_seq ON events(workflow_id,seq);
802
+ CREATE INDEX IF NOT EXISTS events_attempt ON events(attempt_id,seq);
803
+ CREATE TRIGGER IF NOT EXISTS events_append_only BEFORE UPDATE ON events BEGIN
804
+ SELECT RAISE(ABORT,'events are append-only');
805
+ END;
806
+ -- H9: an archived workflow accepts no more events (today 1,840 events were written after archive), except purge events.
807
+ CREATE TRIGGER IF NOT EXISTS events_refuse_archived BEFORE INSERT ON events
808
+ WHEN (SELECT phase FROM workflows WHERE workflow_id=NEW.workflow_id)='archived' AND NEW.kind NOT LIKE 'purge-%' BEGIN
809
+ SELECT RAISE(ABORT,'workflow-archived: no further writes');
810
+ END;
811
+ CREATE TRIGGER IF NOT EXISTS events_delete_only_by_purge BEFORE DELETE ON events
812
+ WHEN NOT EXISTS(SELECT 1 FROM workflow_purges p WHERE p.workflow_id=OLD.workflow_id AND p.state='deleting') BEGIN
813
+ SELECT RAISE(ABORT,'events are append-only: only the owner-approved workflow purge deletes them');
814
+ END;
815
+
816
+ -- logs: typed logs. Ops write via `starci kernel log` (no more log.jsonl in .starciwork); UI reads only.
817
+ -- 'debug' is NOT written to the DB by default (debug lives in the try's raw log blob); per-workflow opt-in keeps 14 days.
818
+ CREATE TABLE IF NOT EXISTS logs(
819
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
820
+ at INTEGER NOT NULL,
821
+ workflow_id TEXT NOT NULL REFERENCES workflows(workflow_id) ON DELETE CASCADE,
822
+ job_id TEXT,
823
+ attempt_id INTEGER,
824
+ trace_id TEXT, span_id TEXT,
825
+ actor TEXT NOT NULL CHECK(actor IN ('kernel','op','runtime','check','land','settler','reconciler')),
826
+ node_id TEXT,
827
+ level TEXT NOT NULL CHECK(level IN ('debug','info','warn','error')),
828
+ kind TEXT NOT NULL, -- typed-logs.mjs LOG_KINDS
829
+ msg TEXT NOT NULL,
830
+ data_json TEXT CHECK(data_json IS NULL OR json_valid(data_json)),
831
+ refs_json TEXT CHECK(refs_json IS NULL OR json_valid(refs_json)),
832
+ src TEXT UNIQUE) STRICT; -- idempotent derivation key (ev:<seq> ...)
833
+ CREATE INDEX IF NOT EXISTS logs_workflow ON logs(workflow_id,seq);
834
+ CREATE INDEX IF NOT EXISTS logs_attempt ON logs(attempt_id,seq);
835
+ CREATE INDEX IF NOT EXISTS logs_job ON logs(job_id,seq);
836
+ CREATE INDEX IF NOT EXISTS logs_kind ON logs(workflow_id,kind,seq);
837
+ CREATE TRIGGER IF NOT EXISTS logs_append_only_update BEFORE UPDATE ON logs BEGIN
838
+ SELECT RAISE(ABORT,'logs are append-only');
839
+ END;
840
+ CREATE TRIGGER IF NOT EXISTS logs_delete_guard BEFORE DELETE ON logs
841
+ WHEN NOT EXISTS(SELECT 1 FROM workflow_purges p WHERE p.workflow_id=OLD.workflow_id AND p.state='deleting')
842
+ AND NOT (OLD.level='debug' AND OLD.at < CAST(unixepoch('subsec')*1000 AS INTEGER) - 1209600000) BEGIN
843
+ SELECT RAISE(ABORT,'logs are append-only: only the workflow purge (or debug retention > 14 days) deletes them');
844
+ END;
845
+
846
+ -- logs_fts: FTS5 external-content over msg/kind (data_json not indexed). Search: logs_fts MATCH '"EADDRINUSE"'.
847
+ CREATE VIRTUAL TABLE IF NOT EXISTS logs_fts USING fts5(msg, kind, content='logs', content_rowid='seq',
848
+ tokenize='unicode61 remove_diacritics 2');
849
+ CREATE TRIGGER IF NOT EXISTS logs_fts_insert AFTER INSERT ON logs BEGIN
850
+ INSERT INTO logs_fts(rowid,msg,kind) VALUES (NEW.seq,NEW.msg,NEW.kind);
851
+ END;
852
+ CREATE TRIGGER IF NOT EXISTS logs_fts_delete AFTER DELETE ON logs BEGIN
853
+ INSERT INTO logs_fts(logs_fts,rowid,msg,kind) VALUES ('delete',OLD.seq,OLD.msg,OLD.kind);
854
+ END;
855
+
856
+ CREATE TABLE IF NOT EXISTS log_cursors(name TEXT PRIMARY KEY, value INTEGER NOT NULL) STRICT; -- events:<ledger> -> last derived seq
857
+
858
+ -- ---------------------------------------------------------------------------------------------------------
859
+ -- A8. Query views (durable)
860
+ -- ---------------------------------------------------------------------------------------------------------
861
+ -- Native state of a try (computed in one place, used for ui).
862
+ CREATE VIEW IF NOT EXISTS v_attempt_state AS
863
+ SELECT a.attempt_id,
864
+ CASE WHEN a.end_state='worker-dead' THEN 'worker-dead' WHEN a.end_state='effect-unknown' THEN 'effect-unknown'
865
+ WHEN a.end_state='requeued' AND json_extract(a.settle_json,'$.reason')='dispatch-rejected' THEN 'rejected'
866
+ WHEN a.end_state='requeued' THEN 'requeued'
867
+ WHEN (a.verdict='blocked' AND a.report_outcome='ask')
868
+ OR (a.verdict IS NOT NULL AND (SELECT j.status FROM jobs j WHERE j.job_id=a.job_id)='awaiting_owner') THEN 'awaiting-owner'
869
+ WHEN a.verdict IS NOT NULL THEN a.verdict
870
+ WHEN a.dispatched_at IS NULL THEN 'routed' ELSE 'in-flight' END AS native
871
+ FROM op_attempts a;
872
+
873
+ -- Op history: one row per dispatch (UI /api/attempts).
874
+ CREATE VIEW IF NOT EXISTS v_op_history AS
875
+ SELECT a.attempt_id, a.workflow_id, a.unit_id, a.job_id, a.op_id, a.try_no, a.dispatch_seq, a.dispatch_id, a.span_id,
876
+ a.agent, a.provider, a.model, a.model_profile, a.pool, a.effort, a.cli_version, a.runtime_rev,
877
+ a.terminal_handle, a.worktree_path, a.branch, a.base_sha, a.head_sha, a.integrated_sha,
878
+ a.routed_at, a.dispatched_at, a.reported_at, a.settled_at, a.released_at, a.worktree_removed_at,
879
+ COALESCE(a.wall_ms, a.settled_at - a.dispatched_at) AS cycle_ms,
880
+ a.report_outcome, a.verdict, a.settled_by, a.failure_class, a.end_state, a.why_json,
881
+ a.tokens_in, a.tokens_out, a.cost_usd, a.usage_source,
882
+ a.prompt_sha, a.transcript_sha, a.session_sha,
883
+ s.native AS attempt_state,
884
+ CASE WHEN s.native IN ('awaiting-owner','rejected') THEN s.native ELSE COALESCE(m.ui,'unknown') END AS ui,
885
+ j.status AS job_status, r.report_id, r.summary AS report_summary,
886
+ (SELECT count(*) FROM check_runs c WHERE c.attempt_id=a.attempt_id) AS checks,
887
+ (SELECT count(*) FROM check_runs c WHERE c.attempt_id=a.attempt_id AND c.status IN ('fail','error')) AS checks_red,
888
+ (SELECT count(*) FROM job_artifacts x WHERE x.attempt_id=a.attempt_id) AS artifacts
889
+ FROM op_attempts a
890
+ JOIN v_attempt_state s ON s.attempt_id=a.attempt_id
891
+ LEFT JOIN ui_state_map m ON m.entity='attempt' AND m.native=s.native
892
+ LEFT JOIN jobs j ON j.job_id=a.job_id
893
+ LEFT JOIN reports r ON r.attempt_id=a.attempt_id;
894
+
895
+ -- Unit rows with the latest try (UI /units, /graph).
896
+ CREATE VIEW IF NOT EXISTS v_units AS
897
+ SELECT u.*, COALESCE(m.ui,'unknown') AS ui,
898
+ (SELECT max(attempt_id) FROM op_attempts a WHERE a.workflow_id=u.workflow_id AND a.unit_id=u.unit_id) AS last_attempt_id
899
+ FROM work_units u LEFT JOIN ui_state_map m ON m.entity='unit' AND m.native=u.state;
900
+
901
+ -- Checks with ui.
902
+ CREATE VIEW IF NOT EXISTS v_checks AS
903
+ SELECT c.*, COALESCE(m.ui,'unknown') AS ui FROM check_runs c LEFT JOIN ui_state_map m ON m.entity='check' AND m.native=c.status;
904
+
905
+ -- An ENDED workflow (phase archived|finished) surfaces no live leftovers: the predicate of scripts/machine/decisions.mjs listDecisions,
906
+ -- applied per leg here (a missing workflow row survives; v_decision_rows keeps an ended workflow's resolved rows as history).
907
+ -- Decision items with a time-rule ui: overdue or escalations >= 2 -> bad; under 20% of the window left -> warn.
908
+ CREATE VIEW IF NOT EXISTS v_decision_rows AS
909
+ SELECT d.*,
910
+ (d.due_at IS NOT NULL AND d.status IN ('open','claimed','escalated') AND d.due_at < CAST(unixepoch('subsec')*1000 AS INTEGER)) AS overdue,
911
+ CASE WHEN d.status IN ('open','claimed','escalated')
912
+ AND ((d.due_at IS NOT NULL AND d.due_at < CAST(unixepoch('subsec')*1000 AS INTEGER)) OR d.escalations>=2) THEN 'bad'
913
+ WHEN d.status='open' AND d.due_at IS NOT NULL
914
+ AND (d.due_at - CAST(unixepoch('subsec')*1000 AS INTEGER)) < 0.2*(d.due_at - d.opened_at) THEN 'warn'
915
+ ELSE COALESCE(m.ui,'unknown') END AS ui
916
+ FROM decision_items d LEFT JOIN ui_state_map m ON m.entity='decision' AND m.native=d.status
917
+ LEFT JOIN workflows w ON w.workflow_id=d.workflow_id
918
+ WHERE d.status NOT IN ('open','claimed','escalated') OR w.phase IS NULL OR w.phase NOT IN ('archived','finished');
919
+
920
+ -- Media with link.
921
+ CREATE VIEW IF NOT EXISTS v_media AS
922
+ SELECT x.artifact_id, x.workflow_id, x.job_id, x.attempt_id, x.op_id, x.role, x.kind, x.subkind,
923
+ x.name, x.label, x.scope_ref, x.round, x.run_id, x.sha256, x.bytes, x.media_type, x.created_at,
924
+ b.http_path, b.file_uri, b.pinned, b.archived_at
925
+ FROM job_artifacts x JOIN blobs b ON b.sha256=x.sha256
926
+ WHERE x.role IN ('direction','render','redline','capture','dom','screenshot','video','trace','uat-run')
927
+ OR x.media_type LIKE 'image/%' OR x.media_type LIKE 'video/%';
928
+
929
+ -- Evidence of a Work record.
930
+ CREATE VIEW IF NOT EXISTS v_record_evidence AS
931
+ SELECT c.record_id, c.record_path, c.field, c.role, c.record_rev, c.sha256,
932
+ b.http_path, b.file_uri, b.media_type, b.bytes,
933
+ x.artifact_id, x.workflow_id, x.attempt_id, x.job_id, x.op_id, x.label, x.run_id, x.created_at AS produced_at
934
+ FROM work_citations c JOIN blobs b ON b.sha256=c.sha256 LEFT JOIN job_artifacts x ON x.artifact_id=c.artifact_id;
935
+
936
+ -- Timeline: event + log + try milestones + check + decision (ordered by at, then seq).
937
+ CREATE VIEW IF NOT EXISTS v_timeline AS
938
+ SELECT workflow_id, created_at AS at, seq, 'event' AS source, kind, entity_type, entity_id, attempt_id, payload_json AS detail
939
+ FROM events
940
+ UNION ALL
941
+ SELECT workflow_id, at, seq, 'log', level||':'||kind, actor, node_id, attempt_id, msg FROM logs
942
+ UNION ALL
943
+ SELECT workflow_id, COALESCE(settled_at, reported_at, started_at, dispatched_at, routed_at), attempt_id, 'attempt',
944
+ COALESCE(verdict, report_outcome, 'active'), 'attempt', CAST(attempt_id AS TEXT), attempt_id,
945
+ json_object('op',op_id,'try',try_no,'seq',dispatch_seq,'agent',agent,'model',model,'verdict',verdict)
946
+ FROM op_attempts WHERE COALESCE(settled_at, reported_at, started_at, dispatched_at, routed_at) IS NOT NULL
947
+ UNION ALL
948
+ SELECT workflow_id, COALESCE(started_at, created_at), check_id, 'check', name||' '||status, 'check', CAST(check_id AS TEXT),
949
+ attempt_id, command FROM check_runs
950
+ UNION ALL
951
+ SELECT workflow_id, decided_at, 0, 'decision', choice, subject_type, subject_id, NULL, rationale FROM decisions;
952
+
953
+ -- Progress per unit.
954
+ CREATE VIEW IF NOT EXISTS v_workflow_progress AS
955
+ SELECT w.workflow_id, w.display_name, w.phase, w.trace_id, w.allowed_parallel,
956
+ count(u.unit_id) AS units_total,
957
+ coalesce(sum(u.state='done'),0) AS units_done,
958
+ coalesce(sum(u.state IN ('running','reported','deciding')),0) AS units_active,
959
+ coalesce(sum(u.state IN ('planned','queued')),0) AS units_waiting,
960
+ coalesce(sum(u.state='failed'),0) AS units_failed,
961
+ coalesce(sum(u.state='dropped'),0) AS units_dropped,
962
+ CASE WHEN count(u.unit_id)=0 THEN 0.0 ELSE round(1.0*sum(u.state='done')/count(u.unit_id),3) END AS completion_rate,
963
+ coalesce(sum(u.state='done' AND u.done_at > CAST(unixepoch('subsec')*1000 AS INTEGER) - 21600000),0) AS done_last_6h,
964
+ max(u.done_at) AS last_unit_at,
965
+ CASE WHEN coalesce(sum(u.state='done' AND u.done_at > CAST(unixepoch('subsec')*1000 AS INTEGER) - 21600000),0)=0 THEN NULL
966
+ ELSE CAST(unixepoch('subsec')*1000 AS INTEGER) + CAST(21600000.0 * (count(u.unit_id) - sum(u.state IN ('done','dropped')))
967
+ / sum(u.state='done' AND u.done_at > CAST(unixepoch('subsec')*1000 AS INTEGER) - 21600000) AS INTEGER) END AS eta_at
968
+ FROM workflows w LEFT JOIN work_units u ON u.workflow_id=w.workflow_id
969
+ GROUP BY w.workflow_id;
970
+
971
+ -- Model x op performance.
972
+ CREATE VIEW IF NOT EXISTS v_model_scorecard AS
973
+ SELECT agent, model, op_id, count(*) AS attempts,
974
+ sum(verdict='pass') AS pass, sum(verdict='fail') AS fail, sum(verdict='blocked') AS blocked,
975
+ sum(end_state='worker-dead') AS worker_dead,
976
+ round(1.0*sum(verdict='pass')/NULLIF(sum(verdict IS NOT NULL),0),3) AS pass_rate,
977
+ avg(COALESCE(wall_ms, settled_at - dispatched_at)) AS avg_cycle_ms,
978
+ sum(tokens_in) AS tokens_in, sum(tokens_out) AS tokens_out, sum(cost_usd) AS cost_usd
979
+ FROM op_attempts GROUP BY agent, model, op_id;
980
+
981
+ -- "Why is X stuck": every open blocker, one row each, with who must clear it (UI blockedBy).
982
+ CREATE VIEW IF NOT EXISTS v_blocking AS
983
+ SELECT e.workflow_id, 'unit' AS entity_type, e.to_unit AS entity_id, 'unit' AS blocker_type, e.from_unit AS blocker_id,
984
+ 'UpstreamNotDone' AS reason_code, p.state AS detail, p.updated_at AS since, 'kernel' AS who
985
+ FROM unit_edges e JOIN work_units t ON t.workflow_id=e.workflow_id AND t.unit_id=e.to_unit
986
+ JOIN work_units p ON p.workflow_id=e.workflow_id AND p.unit_id=e.from_unit
987
+ LEFT JOIN workflows w ON w.workflow_id=e.workflow_id
988
+ WHERE t.state IN ('planned','queued') AND p.state NOT IN ('done','dropped')
989
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
990
+ UNION ALL
991
+ SELECT d.workflow_id, COALESCE(d.entity_type,'workflow'), COALESCE(d.entity_id,d.workflow_id), 'decision', d.di_id,
992
+ 'DecisionOpen:'||d.kind, d.summary, d.opened_at, d.decider
993
+ FROM decision_items d LEFT JOIN workflows w ON w.workflow_id=d.workflow_id
994
+ WHERE d.status IN ('open','claimed','escalated')
995
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
996
+ UNION ALL
997
+ SELECT c.workflow_id, c.entity_type, c.entity_id, 'condition', c.type, c.reason, c.message, c.last_transition_at, COALESCE(c.owner,'controller')
998
+ FROM conditions c LEFT JOIN workflows w ON w.workflow_id=c.workflow_id
999
+ WHERE c.status<>'True'
1000
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
1001
+ UNION ALL
1002
+ SELECT i.workflow_id, 'job', COALESCE(i.job_id,i.workflow_id), 'incident', i.incident_id, 'IncidentOpen:'||i.kind,
1003
+ COALESCE(i.detail,i.last_progress), i.created_at, i.owner
1004
+ FROM incidents i LEFT JOIN workflows w ON w.workflow_id=i.workflow_id
1005
+ WHERE i.status='open'
1006
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
1007
+ UNION ALL
1008
+ SELECT x.workflow_id, 'attempt', CAST(x.attempt_id AS TEXT), 'question', CAST(x.inbox_id AS TEXT), 'WorkerQuestionPending',
1009
+ json_extract(x.payload_json,'$.question'), x.created_at, 'kernel'
1010
+ FROM inbox x LEFT JOIN workflows w ON w.workflow_id=x.workflow_id
1011
+ WHERE x.kind='worker-question' AND x.status IN ('pending','claimed')
1012
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
1013
+ UNION ALL
1014
+ SELECT s.workflow_id, 'attempt', CAST(s.attempt_id AS TEXT), 'settle-tail', CAST(s.attempt_id AS TEXT), 'SettleTailFailed', s.last_error,
1015
+ COALESCE(s.started_at,s.queued_at), 'controller'
1016
+ FROM settle_tails s LEFT JOIN workflows w ON w.workflow_id=s.workflow_id
1017
+ WHERE s.state='failed'
1018
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
1019
+ UNION ALL
1020
+ SELECT l.workflow_id, 'workflow', l.workflow_id, 'product-land', CAST(l.land_id AS TEXT), 'ProductLand:'||l.result, l.reason,
1021
+ l.started_at, 'kernel'
1022
+ FROM product_lands l LEFT JOIN workflows w ON w.workflow_id=l.workflow_id
1023
+ WHERE l.result IN ('failed','conflict','red')
1024
+ AND NOT EXISTS(SELECT 1 FROM product_lands k WHERE k.workflow_id=l.workflow_id AND k.land_id>l.land_id AND k.result='landed')
1025
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
1026
+ UNION ALL
1027
+ -- H1: a filed report nobody settled within its SLA (the same rows as v_settle_overdue) blocks its job.
1028
+ SELECT a.workflow_id, 'attempt', CAST(a.attempt_id AS TEXT), 'settle', CAST(a.attempt_id AS TEXT),
1029
+ 'SettleOverdue:'||COALESCE(a.report_outcome,'?'), a.job_id, a.reported_at,
1030
+ CASE WHEN a.report_outcome='done' THEN 'settler' ELSE 'kernel' END
1031
+ FROM op_attempts a LEFT JOIN workflows w ON w.workflow_id=a.workflow_id
1032
+ WHERE a.reported_at IS NOT NULL AND a.settled_at IS NULL AND a.end_state IS NULL
1033
+ AND (CAST(unixepoch('subsec')*1000 AS INTEGER) - a.reported_at) > CASE WHEN a.report_outcome='done' THEN 180000 ELSE 900000 END
1034
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'));
1035
+
1036
+ -- Open work (DIs + jobs holding a lease/running).
1037
+ CREATE VIEW IF NOT EXISTS v_open_work AS
1038
+ SELECT 'decision' AS kind, d.workflow_id, d.di_id AS ref, d.summary AS what, d.opened_at AS since, d.due_at
1039
+ FROM decision_items d LEFT JOIN workflows w ON w.workflow_id=d.workflow_id
1040
+ WHERE d.status IN ('open','claimed','escalated')
1041
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'))
1042
+ UNION ALL
1043
+ SELECT 'job-'||j.status, j.workflow_id, j.job_id, j.op_id, j.updated_at, j.deadline
1044
+ FROM jobs j LEFT JOIN workflows w ON w.workflow_id=j.workflow_id
1045
+ WHERE j.status IN ('leased','running','answering','effect_unknown')
1046
+ AND (w.phase IS NULL OR w.phase NOT IN ('archived','finished'));
1047
+
1048
+ -- H1: a filed report unsettled past SLA (green <= 3 min for settler; non-green <= 15 min for Kernel, then DI escalation).
1049
+ -- A job in 'reported'/'deciding' past SLA does NOT count as holding a worker slot (census reads this view to exclude it).
1050
+ CREATE VIEW IF NOT EXISTS v_settle_overdue AS
1051
+ SELECT a.workflow_id, a.attempt_id, a.job_id, a.unit_id, a.op_id, a.report_outcome, j.status AS job_status,
1052
+ a.reported_at, (CAST(unixepoch('subsec')*1000 AS INTEGER) - a.reported_at) AS waiting_ms,
1053
+ CASE WHEN a.report_outcome='done' THEN 'settler' ELSE 'kernel' END AS who,
1054
+ CASE WHEN a.report_outcome='done' THEN 180000 ELSE 900000 END AS sla_ms,
1055
+ (SELECT di_id FROM decision_items d WHERE d.attempt_id=a.attempt_id AND d.status IN ('open','claimed','escalated')) AS open_di
1056
+ FROM op_attempts a JOIN jobs j ON j.job_id=a.job_id
1057
+ WHERE a.reported_at IS NOT NULL AND a.settled_at IS NULL AND a.end_state IS NULL
1058
+ AND (CAST(unixepoch('subsec')*1000 AS INTEGER) - a.reported_at) > CASE WHEN a.report_outcome='done' THEN 180000 ELSE 900000 END;
1059
+
1060
+ -- Leaks: expired leases (with holder) and overdue open incidents - GC/Resource ctrl scan this view (H12).
1061
+ CREATE VIEW IF NOT EXISTS v_ledger_leaks AS
1062
+ SELECT 'lease' AS kind, l.resource_key AS target, l.workflow_id, l.job_id, l.attempt_id, l.holder AS owner, l.expires_at AS due_at
1063
+ FROM leases l WHERE l.expires_at < CAST(unixepoch('subsec')*1000 AS INTEGER)
1064
+ UNION ALL
1065
+ SELECT 'incident', i.incident_id, i.workflow_id, i.job_id, i.attempt_id, i.owner, i.due_at
1066
+ FROM incidents i WHERE i.status='open' AND i.due_at < CAST(unixepoch('subsec')*1000 AS INTEGER)
1067
+ UNION ALL
1068
+ SELECT 'attempt-no-transcript', CAST(a.attempt_id AS TEXT), a.workflow_id, a.job_id, a.attempt_id, a.terminal_handle, a.terminal_closed_at + 120000
1069
+ FROM op_attempts a WHERE a.terminal_closed_at IS NOT NULL AND a.transcript_sha IS NULL
1070
+ AND a.terminal_closed_at < CAST(unixepoch('subsec')*1000 AS INTEGER) - 120000; -- SLA TRANSCRIPT_MISSING
1071
+
1072
+ -- Search by id across every entity (UI /api/search, key part; the text part uses logs_fts).
1073
+ CREATE VIEW IF NOT EXISTS v_search_ids AS
1074
+ SELECT 'workflow' AS kind, workflow_id AS id, workflow_id, COALESCE(display_name,title) AS title FROM workflows
1075
+ UNION ALL SELECT 'trace', trace_id, workflow_id, COALESCE(display_name,title) FROM workflows
1076
+ UNION ALL SELECT 'unit', unit_id, workflow_id, COALESCE(title,op_id) FROM work_units
1077
+ UNION ALL SELECT 'job', job_id, workflow_id, op_id FROM jobs
1078
+ UNION ALL SELECT 'attempt', CAST(attempt_id AS TEXT), workflow_id, op_id||' #'||try_no||'.'||dispatch_seq FROM op_attempts
1079
+ UNION ALL SELECT 'dispatch', dispatch_id, workflow_id, op_id FROM op_attempts
1080
+ UNION ALL SELECT 'span', span_id, workflow_id, op_id FROM op_attempts
1081
+ UNION ALL SELECT 'terminal', terminal_handle, workflow_id, op_id FROM op_attempts WHERE terminal_handle IS NOT NULL
1082
+ UNION ALL SELECT 'commit', head_sha, workflow_id, op_id FROM op_attempts WHERE head_sha IS NOT NULL
1083
+ UNION ALL SELECT 'decision', di_id, workflow_id, summary FROM decision_items
1084
+ UNION ALL SELECT 'incident', incident_id, workflow_id, kind FROM incidents
1085
+ UNION ALL SELECT 'artifact', CAST(artifact_id AS TEXT), workflow_id, name FROM job_artifacts
1086
+ UNION ALL SELECT 'blob', sha256, NULL, media_type FROM blobs;
1087
+
1088
+ -- Invalidation marks for SSE /api/live (server compares with previous; ETag also uses PRAGMA data_version).
1089
+ CREATE VIEW IF NOT EXISTS v_live_marks AS
1090
+ SELECT 'events' AS topic, COALESCE(max(seq),0) AS mark FROM events
1091
+ UNION ALL SELECT 'logs', COALESCE(max(seq),0) FROM logs
1092
+ UNION ALL SELECT 'attempts', COALESCE(max(max(attempt_id), max(COALESCE(settled_at,reported_at,dispatched_at,0))),0) FROM op_attempts
1093
+ UNION ALL SELECT 'decisions', COALESCE(max(max(opened_at), max(COALESCE(resolved_at,0))),0) FROM decision_items;
1094
+
1095
+ -- Blob references in this ledger (convenient lookup; GC uses blob_ref_columns, not this view).
1096
+ CREATE VIEW IF NOT EXISTS v_blob_refs AS
1097
+ SELECT b.sha256, b.bytes, b.pinned, b.archived_at, b.created_at,
1098
+ (SELECT count(*) FROM job_artifacts x WHERE x.sha256=b.sha256)
1099
+ + (SELECT count(*) FROM check_runs c WHERE b.sha256 IN (c.stdout_sha,c.stderr_sha,c.output_sha))
1100
+ + (SELECT count(*) FROM op_attempts a WHERE b.sha256 IN (a.prompt_sha,a.transcript_sha,a.session_sha))
1101
+ + (SELECT count(*) FROM attempt_transcript_snapshots t WHERE t.sha256=b.sha256)
1102
+ + (SELECT count(*) FROM work_citations w WHERE w.sha256=b.sha256)
1103
+ + (SELECT count(*) FROM goal_inputs g WHERE g.sha256=b.sha256)
1104
+ + (SELECT count(*) FROM product_lands l WHERE l.output_sha=b.sha256)
1105
+ + (SELECT count(*) FROM events e WHERE e.payload_sha=b.sha256) AS refs
1106
+ FROM blobs b;
1107
+
1108
+