spine-framework 0.4.4 → 0.5.0-rc.4

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 (283) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/migrate.ts +36 -30
  8. package/.framework/cli/commands/migrations.ts +16 -56
  9. package/.framework/cli/commands/upgrade.ts +35 -0
  10. package/.framework/cli/commands/validate-schema.ts +147 -0
  11. package/.framework/cli/context.ts +223 -110
  12. package/.framework/cli/index.ts +6 -0
  13. package/.framework/cli/lib/project-files.ts +241 -1
  14. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  15. package/.framework/functions/_shared/logging.ts +562 -224
  16. package/.framework/functions/_shared/middleware.ts +47 -44
  17. package/.framework/functions/admin-data.ts +39 -28
  18. package/.framework/functions/ai-agents.ts +10 -6
  19. package/.framework/functions/api-keys.ts +301 -122
  20. package/.framework/functions/apps.ts +43 -26
  21. package/.framework/functions/cil-ingest.ts +48 -15
  22. package/.framework/functions/debug-auth.ts +14 -8
  23. package/.framework/functions/deliveries.ts +39 -12
  24. package/.framework/functions/embeddings.ts +145 -76
  25. package/.framework/functions/integration-outbound.ts +67 -27
  26. package/.framework/functions/integrations.ts +254 -127
  27. package/.framework/functions/logs.ts +273 -133
  28. package/.framework/functions/observability.ts +277 -147
  29. package/.framework/functions/pipeline-executions.ts +1 -1
  30. package/.framework/functions/pipelines.ts +14 -9
  31. package/.framework/functions/prompt-configs.ts +7 -4
  32. package/.framework/functions/roles.ts +8 -5
  33. package/.framework/functions/security.ts +30 -17
  34. package/.framework/functions/system-cron.ts +240 -127
  35. package/.framework/functions/tests.ts +7 -6
  36. package/.framework/functions/timers.ts +310 -107
  37. package/.framework/functions/triggers.ts +28 -13
  38. package/.framework/functions/types.ts +13 -10
  39. package/.framework/migrations/069_log_stats_window.sql +54 -0
  40. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  41. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  42. package/.framework/migrations/072_slug_autofill.sql +77 -0
  43. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  44. package/.framework/src/apps/admin/index.tsx +1141 -775
  45. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  46. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  47. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  48. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  49. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  50. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  51. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  52. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  53. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  54. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  55. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  56. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  57. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  58. package/.framework/src/components/ui/sheet.tsx +24 -26
  59. package/.framework/src/components/ui/textarea.tsx +74 -48
  60. package/.framework/src/hooks/useEntityList.ts +12 -9
  61. package/.framework/src/hooks/useListSchema.ts +108 -58
  62. package/.framework/src/instructions/README.md +57 -0
  63. package/.framework/src/instructions/ai-agents.md +69 -0
  64. package/.framework/src/instructions/alerts.md +80 -0
  65. package/.framework/src/instructions/api-keys.md +87 -0
  66. package/.framework/src/instructions/apps.md +77 -0
  67. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  68. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  69. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  70. package/.framework/src/instructions/concepts/automation.md +186 -0
  71. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  72. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  73. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  74. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  75. package/.framework/src/instructions/concepts/observability.md +116 -0
  76. package/.framework/src/instructions/concepts/permissions.md +150 -0
  77. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  78. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  79. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  80. package/.framework/src/instructions/deliveries.md +59 -0
  81. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  82. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  83. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  84. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  85. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  86. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  87. package/.framework/src/instructions/integrations.md +96 -0
  88. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  89. package/.framework/src/instructions/invites.md +60 -0
  90. package/.framework/src/instructions/kb.md +80 -0
  91. package/.framework/src/instructions/logs.md +77 -0
  92. package/.framework/src/instructions/observability.md +61 -0
  93. package/.framework/src/instructions/operations/deployment.md +115 -0
  94. package/.framework/src/instructions/operations/migrations.md +125 -0
  95. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  96. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  97. package/.framework/src/instructions/operations/upgrade.md +116 -0
  98. package/.framework/src/instructions/pipeline-executions.md +62 -0
  99. package/.framework/src/instructions/pipelines.md +78 -0
  100. package/.framework/src/instructions/prompts.md +95 -0
  101. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  102. package/.framework/src/instructions/reference/api/auth.md +113 -0
  103. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  104. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  105. package/.framework/src/instructions/reference/cli.md +124 -0
  106. package/.framework/src/instructions/reference/database.md +132 -0
  107. package/.framework/src/instructions/reference/design-schema.md +280 -0
  108. package/.framework/src/instructions/reference/env-vars.md +133 -0
  109. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  110. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  111. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  112. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  113. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  114. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  115. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  116. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  117. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  118. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  119. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  120. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  121. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  122. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  123. package/.framework/src/instructions/reference/functions/index.md +103 -0
  124. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  125. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  126. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  127. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  128. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  129. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  130. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  131. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  132. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  133. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  134. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  135. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  136. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  137. package/.framework/src/instructions/reference/functions/security.md +140 -0
  138. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  139. package/.framework/src/instructions/reference/functions/system.md +106 -0
  140. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  141. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  142. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  143. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  144. package/.framework/src/instructions/reference/functions/types.md +146 -0
  145. package/.framework/src/instructions/reference/seeds.md +126 -0
  146. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  147. package/.framework/src/instructions/reference/spine-config.md +114 -0
  148. package/.framework/src/instructions/roles.md +80 -0
  149. package/.framework/src/instructions/runtime-detail.md +70 -0
  150. package/.framework/src/instructions/runtime-list.md +77 -0
  151. package/.framework/src/instructions/security.md +67 -0
  152. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  153. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  154. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  155. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  156. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  157. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  158. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  159. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  160. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  161. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  162. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  163. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  164. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  165. package/.framework/src/instructions/testing.md +59 -0
  166. package/.framework/src/instructions/timers.md +82 -0
  167. package/.framework/src/instructions/triggers.md +69 -0
  168. package/.framework/src/instructions/types.md +93 -0
  169. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  170. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  171. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  172. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  173. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  174. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  175. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  176. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  177. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  178. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  179. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  180. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  181. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  182. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  183. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  184. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  185. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  186. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  187. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  188. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  189. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  190. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  191. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  192. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  193. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  194. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  195. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  196. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  197. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  198. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  199. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  200. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  201. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  202. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  203. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  204. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  205. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  206. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  207. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  208. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  209. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  210. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  211. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  212. package/.framework/templates/agent/AGENTS.md +150 -0
  213. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  214. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  215. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  216. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  217. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  218. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  219. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  220. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  221. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  222. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  223. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  224. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  225. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  226. package/.framework/templates/project.json +4 -1
  227. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  228. package/.framework/tests/unit/agent-files.test.ts +355 -0
  229. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  230. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  231. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  232. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  233. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  234. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  235. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  236. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  237. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  238. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  239. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  240. package/.framework/tests/unit/system-cron.test.ts +240 -0
  241. package/.framework/tests/unit/timers.test.ts +159 -0
  242. package/README.md +125 -166
  243. package/config/tsconfig.cli.json +2 -2
  244. package/package.json +15 -4
  245. package/templates/project/package-lock.json +2 -2
  246. package/.framework/docs/admin-routes.md +0 -76
  247. package/.framework/docs/api-docs-progress.md +0 -38
  248. package/.framework/docs/api-governance.md +0 -146
  249. package/.framework/docs/api-testing-results.md +0 -212
  250. package/.framework/docs/assembly-launch-guide.md +0 -138
  251. package/.framework/docs/audit-results.md +0 -590
  252. package/.framework/docs/db-api-inventory.md +0 -95
  253. package/.framework/docs/gap-remediation-backlog.md +0 -103
  254. package/.framework/docs/ui-adoption-verification.md +0 -111
  255. package/.framework/docs/ui-api-coverage.md +0 -84
  256. package/.framework/docs/v2-compatibility-audit.md +0 -228
  257. package/.framework/mcp/index.ts +0 -133
  258. package/.framework/mcp/load-env.ts +0 -60
  259. package/.framework/mcp/shared/app-manifest.ts +0 -275
  260. package/.framework/mcp/shared/db.ts +0 -477
  261. package/.framework/mcp/shared/kb.ts +0 -118
  262. package/.framework/mcp/tools/architect.ts +0 -431
  263. package/.framework/mcp/tools/cil/context.ts +0 -345
  264. package/.framework/mcp/tools/cil/graph.ts +0 -242
  265. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  266. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  267. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  268. package/.framework/mcp/tools/cil/project.ts +0 -791
  269. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  270. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  271. package/.framework/mcp/tools/cil/search.ts +0 -97
  272. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  273. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  274. package/.framework/mcp/tools/kb/delete.ts +0 -139
  275. package/.framework/mcp/tools/kb/index.ts +0 -52
  276. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  277. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  278. package/.framework/mcp/tools/kb/publish.ts +0 -100
  279. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  280. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  281. package/.framework/mcp/tools/kb/search.ts +0 -101
  282. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  283. package/.framework/mcp/tools/observability.ts +0 -510
@@ -33,10 +33,31 @@ export interface ProjectProfile {
33
33
  config_files: string[]
34
34
  root_scripts: string[]
35
35
  bin: string[]
36
+ /**
37
+ * Agent-facing assets init copies into the customer repo. Unlike the
38
+ * categories above, these are NOT copied verbatim into a matching path —
39
+ * each entry names a package `source`, a project `dest`, and a `merge`
40
+ * mode handled by syncAgentFiles:
41
+ * framework-block — render the template, substitute {{SPINE_VERSION}},
42
+ * and merge only the marked framework block into dest (init writes the
43
+ * whole file; upgrade replaces the block between the markers and
44
+ * preserves everything else).
45
+ * dir-replace — copy the source directory tree to dest; on every run,
46
+ * `spine-*` subdirectories under dest are removed first, so framework
47
+ * skills are replaced wholesale while customer-owned sibling dirs
48
+ * (any name not starting with `spine-`) are left alone.
49
+ */
50
+ agent_files?: AgentFileEntry[]
36
51
  exclude_globs: string[]
37
52
  package_scripts: Record<string, string>
38
53
  }
39
54
 
55
+ export interface AgentFileEntry {
56
+ source: string
57
+ dest: string
58
+ merge: 'framework-block' | 'dir-replace'
59
+ }
60
+
40
61
  export interface CollectedFile {
41
62
  rel: string
42
63
  abs: string
@@ -280,7 +301,11 @@ export function mergePackageJson(opts: {
280
301
  }
281
302
  for (const [k, v] of Object.entries(opts.newDependencies)) dependencies[k] = v
282
303
  for (const [k, v] of Object.entries(opts.newDevDependencies)) devDependencies[k] = v
283
- dependencies['spine-framework'] = opts.spineVersion
304
+ const existingSpec = dependencies['spine-framework']
305
+ dependencies['spine-framework'] =
306
+ existingSpec && /^(file:|link:|workspace:|portal:)/.test(existingSpec)
307
+ ? existingSpec
308
+ : opts.spineVersion
284
309
 
285
310
  const frameworkDeps = [
286
311
  ...Object.keys(opts.newDependencies),
@@ -322,6 +347,7 @@ export function writeFrozenManifest(opts: {
322
347
  projectRoot: string
323
348
  frameworkVersion: string
324
349
  frameworkDeps: string[]
350
+ agentFiles?: Record<string, string>
325
351
  }): { fileCount: number; treeDigest: string } {
326
352
  const { projectRoot } = opts
327
353
  const entries = collectProtectedFiles(projectRoot, ['bin'], new Set(['.spine-manifest.json']))
@@ -338,6 +364,9 @@ export function writeFrozenManifest(opts: {
338
364
  node: '>=22.13.0',
339
365
  npm: '>=10.0.0',
340
366
  files,
367
+ // Agent assets live outside `files` — see the agent-files block comment
368
+ // above for why sealing them would break verify and upgrade.
369
+ ...(opts.agentFiles ? { agent_files: opts.agentFiles } : {}),
341
370
  }
342
371
  fs.writeFileSync(
343
372
  path.join(projectRoot, '.spine-manifest.json'),
@@ -347,6 +376,217 @@ export function writeFrozenManifest(opts: {
347
376
  }
348
377
  // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:manifest:block"]
349
378
 
379
+ /*
380
+ * Agent asset sync — AGENTS.md framework block + .devin/skills/spine-*.
381
+ *
382
+ * Marker contract (upgrade.ts, doctor.ts, and tests depend on this format):
383
+ *
384
+ * <!-- spine-framework:begin version=X.Y.Z -->
385
+ * ...framework-owned lines...
386
+ * <!-- spine-framework:end -->
387
+ *
388
+ * - The markers are HTML comments so they are inert in rendered markdown.
389
+ * - `version=` on the begin marker lets doctor/doctor-style checks read the
390
+ * block's framework version without hashing.
391
+ * - {{SPINE_VERSION}} anywhere in the template (including inside the begin
392
+ * marker) is substituted at copy time by renderAgentTemplate.
393
+ * - Everything outside the markers is customer-owned and never rewritten.
394
+ *
395
+ * Manifest semantics: agent files are hashed into the manifest's
396
+ * `agent_files` map — NOT `files`. Keeping them out of `files` matters:
397
+ * verify-manifest treats `files` entries as sealed and unions their top-level
398
+ * dirs into the protected trees, which would flag every customer file under
399
+ * .devin/ as EXTRA and every edit to AGENTS.md's user section as MODIFIED.
400
+ * `agent_files` records, per dest rel:
401
+ * - framework-block dests: sha256 of the extracted block (LF-normalized),
402
+ * so edits outside the markers do not count as drift.
403
+ * - dir-replace dests: sha256 of each file under `spine-*` subdirs.
404
+ */
405
+
406
+ /** Begin marker: `<!-- spine-framework:begin version=X.Y.Z -->` (version optional on read). */
407
+ export const AGENTS_BEGIN_RE = /<!--\s*spine-framework:begin(?:\s+version=([^\s>]*))?\s*-->/
408
+ /** End marker: `<!-- spine-framework:end -->`. */
409
+ export const AGENTS_END_RE = /<!--\s*spine-framework:end\s*-->/
410
+
411
+ /** Substitute {{SPINE_VERSION}} placeholders in an agent template. */
412
+ export function renderAgentTemplate(template: string, version: string): string {
413
+ return template.replace(/\{\{SPINE_VERSION\}\}/g, version)
414
+ }
415
+
416
+ /**
417
+ * Locate the framework block inside newline-split lines.
418
+ * Returns inclusive line indices, or null when no begin marker exists.
419
+ * An unterminated begin marker spans only its own line — the orphan marker is
420
+ * consumed on merge while the stale body below degrades into user content
421
+ * (safe: user content is never deleted, only the framework block is managed).
422
+ */
423
+ export function findAgentsBlock(lines: string[]): { begin: number; end: number } | null {
424
+ let begin = -1
425
+ for (let i = 0; i < lines.length; i++) {
426
+ if (AGENTS_BEGIN_RE.test(lines[i])) { begin = i; break }
427
+ }
428
+ if (begin === -1) return null
429
+ for (let i = begin + 1; i < lines.length; i++) {
430
+ if (AGENTS_END_RE.test(lines[i])) return { begin, end: i }
431
+ }
432
+ return { begin, end: begin }
433
+ }
434
+
435
+ /** The framework block's declared version, or null when absent. */
436
+ export function agentsBlockVersion(text: string): string | null {
437
+ const m = text.match(AGENTS_BEGIN_RE)
438
+ return m ? (m[1] ?? null) : null
439
+ }
440
+
441
+ export interface AgentsMergeResult {
442
+ text: string
443
+ status: 'replaced' | 'prepended'
444
+ }
445
+
446
+ /**
447
+ * Merge the rendered template's framework block into existing AGENTS.md text.
448
+ * Replaces the marker-delimited span when markers exist (EOL preserved),
449
+ * otherwise prepends the block ahead of the user's content.
450
+ */
451
+ export function mergeAgentsFile(existing: string, renderedTemplate: string): AgentsMergeResult {
452
+ const eol = existing.includes('\r\n') ? '\r\n' : '\n'
453
+ const tplLines = renderedTemplate.split(/\r?\n/)
454
+ const tplRange = findAgentsBlock(tplLines)
455
+ if (!tplRange) {
456
+ throw new Error('agent template is missing spine-framework begin/end markers')
457
+ }
458
+ const blockLines = tplLines.slice(tplRange.begin, tplRange.end + 1)
459
+
460
+ const lines = existing.split(/\r?\n/)
461
+ const range = findAgentsBlock(lines)
462
+ if (!range) {
463
+ const body = existing.replace(/^\s+/, '')
464
+ return { text: `${blockLines.join(eol)}${eol}${eol}${body}`, status: 'prepended' }
465
+ }
466
+ const next = [...lines.slice(0, range.begin), ...blockLines, ...lines.slice(range.end + 1)]
467
+ return { text: next.join(eol), status: 'replaced' }
468
+ }
469
+
470
+ export interface AgentSyncAction {
471
+ rel: string
472
+ action: 'written' | 'replaced' | 'prepended' | 'synced' | 'unchanged' | 'skipped'
473
+ detail?: string
474
+ }
475
+
476
+ function copyDirRecursive(srcAbs: string, destAbs: string): void {
477
+ fs.mkdirSync(destAbs, { recursive: true })
478
+ for (const dent of fs.readdirSync(srcAbs, { withFileTypes: true })) {
479
+ const src = path.join(srcAbs, dent.name)
480
+ const dest = path.join(destAbs, dent.name)
481
+ if (dent.isDirectory()) copyDirRecursive(src, dest)
482
+ else if (dent.isFile()) {
483
+ fs.copyFileSync(src, dest)
484
+ fs.chmodSync(dest, fs.statSync(src).mode)
485
+ }
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Apply the profile's agent_files entries to a project tree.
491
+ * `framework-block` renders + merges the template; `dir-replace` removes
492
+ * `spine-*` subdirs under dest then copies the source tree in.
493
+ * dryRun reports the same actions without writing.
494
+ */
495
+ export function syncAgentFiles(opts: {
496
+ pkgRoot: string
497
+ projectRoot: string
498
+ entries: AgentFileEntry[]
499
+ version: string
500
+ dryRun?: boolean
501
+ }): AgentSyncAction[] {
502
+ const out: AgentSyncAction[] = []
503
+ for (const entry of opts.entries ?? []) {
504
+ const srcAbs = path.join(opts.pkgRoot, entry.source)
505
+ const destAbs = path.join(opts.projectRoot, entry.dest)
506
+
507
+ if (entry.merge === 'framework-block') {
508
+ if (!fs.existsSync(srcAbs)) {
509
+ out.push({ rel: entry.dest, action: 'skipped', detail: 'template absent from package' })
510
+ continue
511
+ }
512
+ const rendered = renderAgentTemplate(fs.readFileSync(srcAbs, 'utf8'), opts.version)
513
+ if (!fs.existsSync(destAbs)) {
514
+ if (!opts.dryRun) {
515
+ fs.mkdirSync(path.dirname(destAbs), { recursive: true })
516
+ fs.writeFileSync(destAbs, rendered)
517
+ }
518
+ out.push({ rel: entry.dest, action: 'written' })
519
+ } else {
520
+ const existing = fs.readFileSync(destAbs, 'utf8')
521
+ const merged = mergeAgentsFile(existing, rendered)
522
+ if (merged.text === existing) {
523
+ out.push({ rel: entry.dest, action: 'unchanged' })
524
+ } else {
525
+ if (!opts.dryRun) fs.writeFileSync(destAbs, merged.text)
526
+ out.push({ rel: entry.dest, action: merged.status })
527
+ }
528
+ }
529
+ } else if (entry.merge === 'dir-replace') {
530
+ if (!fs.existsSync(srcAbs) || !fs.statSync(srcAbs).isDirectory()) {
531
+ out.push({ rel: entry.dest, action: 'skipped', detail: 'source dir absent from package' })
532
+ continue
533
+ }
534
+ // Framework owns only the spine-* subdirs of dest — remove them, then
535
+ // copy the source tree in. Sibling dirs with any other name are
536
+ // customer-owned and untouched.
537
+ const removed = fs.existsSync(destAbs)
538
+ ? fs.readdirSync(destAbs, { withFileTypes: true })
539
+ .filter((d) => d.isDirectory() && d.name.startsWith('spine-'))
540
+ .map((d) => d.name)
541
+ : []
542
+ if (!opts.dryRun) {
543
+ for (const name of removed) {
544
+ fs.rmSync(path.join(destAbs, name), { recursive: true, force: true })
545
+ }
546
+ copyDirRecursive(srcAbs, destAbs)
547
+ }
548
+ const detail = removed.length ? `removed spine-* dirs: ${removed.join(', ')}` : undefined
549
+ out.push({ rel: entry.dest, action: 'synced', detail })
550
+ } else {
551
+ out.push({ rel: entry.dest, action: 'skipped', detail: `unknown merge mode '${entry.merge}'` })
552
+ }
553
+ }
554
+ return out
555
+ }
556
+
557
+ /**
558
+ * Hash the project's installed agent files for the manifest `agent_files` map.
559
+ * framework-block → hash of the extracted block (LF-normalized, so EOL style
560
+ * does not matter); dir-replace → one entry per file under `spine-*` subdirs.
561
+ */
562
+ export function collectAgentFileHashes(
563
+ projectRoot: string,
564
+ entries: AgentFileEntry[],
565
+ ): Record<string, string> {
566
+ const hashes: Record<string, string> = {}
567
+ for (const entry of entries ?? []) {
568
+ const destAbs = path.join(projectRoot, entry.dest)
569
+ if (entry.merge === 'framework-block') {
570
+ if (!fs.existsSync(destAbs)) continue
571
+ const lines = fs.readFileSync(destAbs, 'utf8').split(/\r?\n/)
572
+ const range = findAgentsBlock(lines)
573
+ if (!range) continue
574
+ hashes[entry.dest] = sha256Text(lines.slice(range.begin, range.end + 1).join('\n') + '\n')
575
+ } else if (entry.merge === 'dir-replace') {
576
+ if (!fs.existsSync(destAbs)) continue
577
+ const managed = fs.readdirSync(destAbs, { withFileTypes: true })
578
+ .filter((d) => d.isDirectory() && d.name.startsWith('spine-'))
579
+ for (const dir of managed) {
580
+ const dirAbs = path.join(destAbs, dir.name)
581
+ for (const f of walkDir(dirAbs, projectRoot, [])) {
582
+ hashes[f.rel] = sha256File(f.abs)
583
+ }
584
+ }
585
+ }
586
+ }
587
+ return hashes
588
+ }
589
+
350
590
  // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:file_io:block" type="block" identity="file_io" parent="~/.framework/cli/lib/project-files.ts"]
351
591
  // CIL[DOC-MACRO purpose="Copy collected files (mode-preserving, symlink safety-checked), sanitize dir names to npm package names, compare semvers"]
352
592
  // CIL[DOC-CONTEXT module="cli" layer="lib" related_files=""]
@@ -1,81 +1,135 @@
1
1
  // CIL[BEGIN stable_id="~/.framework/functions/_shared/hash-api-key.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/functions/_shared/hash-api-key.ts"]
2
- // CIL[DOC-MACRO purpose="Provides PBKDF2-based API key hashing and verification utilities"]
3
- // CIL[DOC-RESPONSIBILITY responsibility="Imports crypto primitives and exports hashApiKey and verifyApiKey"]
4
- // CIL[DOC-CONTEXT module="functions/_shared" layer="shared" related_files=".framework/functions/_shared/hash-api-key.ts"]
5
- // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
6
- // CIL[DOC-MICRO logic_summary="Imports crypto functions used by hashing and verification" notable_decisions="[]"]
7
- // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
8
- // CIL[DOC-STABILITY stability="stable"]
9
- // CIL[DOC-AUDIENCE audience="developers"]
2
+ /**
3
+ * @module hash-api-key
4
+ * @audience both
5
+ * @layer shared-core
6
+ * @stability stable
7
+ *
8
+ * This module provides utilities for securely hashing API keys using cryptographic functions. It plays a crucial role in ensuring the integrity and confidentiality of sensitive data within the application. INVARIANT: The API keys must always be hashed before storage to prevent unauthorized access.
9
+ *
10
+ * @seeAlso crypto (provides cryptographic functions for hashing).
11
+ *
12
+ * @cil-generated
13
+ */
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
10
23
 
11
24
 
12
25
  import { randomBytes, pbkdf2Sync, timingSafeEqual } from 'crypto'
13
- // CIL[DOC-VECTOR type="context" content="purpose='Provides PBKDF2-based API key hashing and verification utilities'; No verified external callers; imports: none"]
14
- // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
15
- // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
16
- // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
17
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
18
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
19
26
  // CIL[END stable_id="~/.framework/functions/_shared/hash-api-key.ts:module_header:module_header"]
20
27
 
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
21
43
  // CIL[BEGIN stable_id="~/.framework/functions/_shared/hash-api-key.ts:ITERATIONS:constant" type="constant" identity="ITERATIONS" parent="~/.framework/functions/_shared/hash-api-key.ts"]
22
- // CIL[DOC-MACRO purpose="Defines the default PBKDF2 iteration count"]
23
- // CIL[DOC-RESPONSIBILITY responsibility="Stores the default number of PBKDF2 iterations for hashing"]
24
- // CIL[DOC-CONTEXT module="functions/_shared" layer="shared" related_files=".framework/functions/_shared/hash-api-key.ts"]
25
- // CIL[DOC-CONTRACT inputs="none" outputs="number" errors="none" side_effects="none"]
26
- // CIL[DOC-MICRO logic_summary="Constant default iteration count used by hashApiKey" notable_decisions="[]"]
27
- // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
28
- // CIL[DOC-STABILITY stability="stable"]
44
+ /**
45
+ * ITERATIONS defines the number of iterations used for hashing an API key, ensuring a high level of security. This constant is crucial for maintaining the strength of the hashing process.
46
+ *
47
+ * @sideEffects none
48
+ * @stability internal
49
+ * @cil-generated
50
+ */
29
51
  const ITERATIONS = 100000
30
- // CIL[DOC-VECTOR type="context" content="purpose='Defines the default PBKDF2 iteration count'; No verified external callers; imports: none"]
31
- // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
32
- // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
33
- // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
34
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
35
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
36
52
  // CIL[END stable_id="~/.framework/functions/_shared/hash-api-key.ts:ITERATIONS:constant"]
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
37
68
  // CIL[BEGIN stable_id="~/.framework/functions/_shared/hash-api-key.ts:KEY_LENGTH:constant" type="constant" identity="KEY_LENGTH" parent="~/.framework/functions/_shared/hash-api-key.ts"]
38
- // CIL[DOC-MACRO purpose="Defines the derived key length in bytes for PBKDF2"]
39
- // CIL[DOC-RESPONSIBILITY responsibility="Stores the PBKDF2 derived key length"]
40
- // CIL[DOC-CONTEXT module="functions/_shared" layer="shared" related_files=".framework/functions/_shared/hash-api-key.ts"]
41
- // CIL[DOC-CONTRACT inputs="none" outputs="number" errors="none" side_effects="none"]
42
- // CIL[DOC-MICRO logic_summary="Constant derived key length used by hashApiKey" notable_decisions="[]"]
43
- // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
44
- // CIL[DOC-STABILITY stability="stable"]
69
+ /**
70
+ * KEY_LENGTH defines the length of the API key used for hashing to ensure a consistent key size for security purposes.
71
+ *
72
+ * @sideEffects none
73
+ * @stability internal
74
+ * @cil-generated
75
+ */
45
76
  const KEY_LENGTH = 32
46
- // CIL[DOC-VECTOR type="context" content="purpose='Defines the derived key length in bytes for PBKDF2'; No verified external callers; imports: none"]
47
- // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
48
- // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
49
- // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
50
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
51
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
52
77
  // CIL[END stable_id="~/.framework/functions/_shared/hash-api-key.ts:KEY_LENGTH:constant"]
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
53
93
  // CIL[BEGIN stable_id="~/.framework/functions/_shared/hash-api-key.ts:DIGEST:constant" type="constant" identity="DIGEST" parent="~/.framework/functions/_shared/hash-api-key.ts"]
54
- // CIL[DOC-MACRO purpose="Defines the digest algorithm for PBKDF2"]
55
- // CIL[DOC-RESPONSIBILITY responsibility="Stores the PBKDF2 digest algorithm"]
56
- // CIL[DOC-CONTEXT module="functions/_shared" layer="shared" related_files=".framework/functions/_shared/hash-api-key.ts"]
57
- // CIL[DOC-CONTRACT inputs="none" outputs="string" errors="none" side_effects="none"]
58
- // CIL[DOC-MICRO logic_summary="Constant digest algorithm used by hashApiKey and verifyApiKey" notable_decisions="[]"]
59
- // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
60
- // CIL[DOC-STABILITY stability="stable"]
94
+ /**
95
+ * DIGEST defines the hashing algorithm used for API key generation, specifically SHA-256. It provides a consistent and secure method for hashing within the module.
96
+ *
97
+ * @sideEffects none
98
+ * @stability internal
99
+ * @cil-generated
100
+ */
61
101
  const DIGEST = 'sha256'
62
- // CIL[DOC-VECTOR type="context" content="purpose='Defines the digest algorithm for PBKDF2'; No verified external callers; imports: none"]
63
- // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
64
- // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
65
- // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
66
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
67
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
68
102
  // CIL[END stable_id="~/.framework/functions/_shared/hash-api-key.ts:DIGEST:constant"]
69
103
 
70
104
 
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
71
120
  // CIL[BEGIN stable_id="~/.framework/functions/_shared/hash-api-key.ts:hashApiKey:function" type="function" identity="hashApiKey" parent="~/.framework/functions/_shared/hash-api-key.ts"]
72
- // CIL[DOC-MACRO purpose="Hash a raw API key using PBKDF2 and return a storable key record"]
73
- // CIL[DOC-RESPONSIBILITY responsibility="Derive a salted PBKDF2 hash, prefix the raw key, and return a hash string"]
74
- // CIL[DOC-CONTEXT module="functions/_shared" layer="shared" related_files=".framework/functions/_shared/hash-api-key.ts"]
75
- // CIL[DOC-CONTRACT inputs="rawKey: string, iterations = ITERATIONS" outputs="{ key_prefix: string, key_hash: string, rawKey: string }" errors="none" side_effects="Uses crypto.randomBytes and pbkdf2Sync to generate salt and hash"]
76
- // CIL[DOC-MICRO logic_summary="Extracts an 8-character prefix, generates a random salt, derives a 32-byte PBKDF2 hash, and encodes the result as pbkdf2$iterations$salt$hash" notable_decisions="[]"]
77
- // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
78
- // CIL[DOC-STABILITY stability="stable"]
121
+ /**
122
+ * Hashes a raw API key using PBKDF2 to enhance security and returns the key prefix along with the hashed key. This function is essential for securely storing API keys.
123
+ *
124
+ * @inputSpec
125
+ * - rawKey: string (required) — the raw API key to be hashed.
126
+ * - iterations: number (optional) — the number of iterations for the PBKDF2 algorithm, defaults to ITERATIONS.
127
+ * @outputSpec { key_prefix: string; key_hash: string; rawKey: string }
128
+ * @sideEffects none
129
+ * @stability internal
130
+ * @calls randomBytes, pbkdf2Sync
131
+ * @cil-generated
132
+ */
79
133
  export function hashApiKey(rawKey: string, iterations = ITERATIONS): { key_prefix: string; key_hash: string; rawKey: string } {
80
134
  const key_prefix = rawKey.slice(0, 8)
81
135
  const salt = randomBytes(16)
@@ -84,23 +138,37 @@ export function hashApiKey(rawKey: string, iterations = ITERATIONS): { key_prefi
84
138
 
85
139
  return { key_prefix, key_hash, rawKey }
86
140
  }
87
- // CIL[DOC-VECTOR type="context" content="purpose='Hash a raw API key using PBKDF2 and return a storable key record'; No verified external callers; imports: none"]
88
- // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
89
- // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
90
- // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
91
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
92
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
93
141
  // CIL[END stable_id="~/.framework/functions/_shared/hash-api-key.ts:hashApiKey:function"]
94
142
 
95
143
 
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
96
159
  // CIL[BEGIN stable_id="~/.framework/functions/_shared/hash-api-key.ts:verifyApiKey:function" type="function" identity="verifyApiKey" parent="~/.framework/functions/_shared/hash-api-key.ts"]
97
- // CIL[DOC-MACRO purpose="Verify a raw API key against a stored PBKDF2 hash"]
98
- // CIL[DOC-RESPONSIBILITY responsibility="Parse the stored hash, re-derive the key, and compare hashes timing-safely"]
99
- // CIL[DOC-CONTEXT module="functions/_shared" layer="shared" related_files=".framework/functions/_shared/hash-api-key.ts"]
100
- // CIL[DOC-CONTRACT inputs="rawKey: string, keyHash: string" outputs="boolean" errors="Returns false for invalid format, missing parts, non-positive iterations, base64 decode errors, or mismatched lengths" side_effects="Uses crypto.pbkdf2Sync and timingSafeEqual"]
101
- // CIL[DOC-MICRO logic_summary="Validates the keyHash format, extracts iterations and salt, derives the hash with the stored length, and compares with timingSafeEqual" notable_decisions="[]"]
102
- // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
103
- // CIL[DOC-STABILITY stability="stable"]
160
+ /**
161
+ * Verifies an API key against a stored hash to ensure its validity. This function is essential for securing access to resources by validating the provided key.
162
+ *
163
+ * @inputSpec
164
+ * - rawKey: string (required) — the API key to verify.
165
+ * - keyHash: string (required) — the hashed representation of the API key.
166
+ * @outputSpec boolean — returns true if the API key is valid, otherwise false.
167
+ * @sideEffects none
168
+ * @stability internal
169
+ * @calls pbkdf2Sync, timingSafeEqual
170
+ * @cil-generated
171
+ */
104
172
  export function verifyApiKey(rawKey: string, keyHash: string): boolean {
105
173
  if (!keyHash || !keyHash.startsWith('pbkdf2$')) {
106
174
  return false
@@ -131,10 +199,46 @@ export function verifyApiKey(rawKey: string, keyHash: string): boolean {
131
199
  return false
132
200
  }
133
201
  }
134
- // CIL[DOC-VECTOR type="context" content="purpose='Verify a raw API key against a stored PBKDF2 hash'; No verified external callers; imports: none"]
135
- // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
136
- // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
137
- // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
138
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
139
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
140
202
  // CIL[END stable_id="~/.framework/functions/_shared/hash-api-key.ts:verifyApiKey:function"]
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+ // CIL[BEGIN stable_id="~/.framework/functions/_shared/hash-api-key.ts:omitSecretFields:function" type="function" identity="omitSecretFields" parent="~/.framework/functions/_shared/hash-api-key.ts"]
220
+ /**
221
+ * The omitSecretFields function removes sensitive fields from an object to prevent exposure of secret information. It is designed to sanitize data before further processing or transmission.
222
+ *
223
+ * @inputSpec
224
+ * row: Record<string, any> | null | undefined — the object from which secret fields will be omitted.
225
+ * @outputSpec Record<string, any> | null | undefined — a new object without the omitted fields, or the original input if it was not an object.
226
+ * @sideEffects none
227
+ * @stability internal
228
+ * @cil-generated
229
+ */
230
+ export function omitSecretFields(row: Record<string, any> | null | undefined) {
231
+ if (!row || typeof row !== 'object') return row
232
+ const copy = { ...row }
233
+ delete copy.key_value
234
+ delete copy.key_hash
235
+ return copy
236
+ }
237
+ // CIL[END stable_id="~/.framework/functions/_shared/hash-api-key.ts:omitSecretFields:function"]
238
+
239
+
240
+
241
+
242
+
243
+
244
+