dsh-hams 0.1.3 → 0.1.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 (50) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +4 -0
  3. package/dist/index.js +11335 -0
  4. package/package.json +9 -5
  5. package/src/domain/check.ts +0 -193
  6. package/src/domain/errors.ts +0 -59
  7. package/src/domain/evidence.ts +0 -134
  8. package/src/domain/ids.ts +0 -31
  9. package/src/host/chat-runtime.ts +0 -489
  10. package/src/host/dsh-adapter.ts +0 -867
  11. package/src/host/session-adapter.ts +0 -276
  12. package/src/http/handler.ts +0 -1132
  13. package/src/index.js +0 -53
  14. package/src/service/audit.ts +0 -92
  15. package/src/service/chat.ts +0 -1100
  16. package/src/service/contracts.ts +0 -193
  17. package/src/service/defaults.ts +0 -182
  18. package/src/service/deliverables.ts +0 -240
  19. package/src/service/hams.ts +0 -3103
  20. package/src/service/knowledge.ts +0 -940
  21. package/src/service/member-config.ts +0 -39
  22. package/src/service/members.ts +0 -506
  23. package/src/service/memory.ts +0 -375
  24. package/src/service/permissions.ts +0 -118
  25. package/src/service/project-lifecycle.ts +0 -233
  26. package/src/service/projects.ts +0 -840
  27. package/src/service/receipts.ts +0 -63
  28. package/src/service/templates.ts +0 -140
  29. package/src/service/tick.ts +0 -751
  30. package/src/service/tools.ts +0 -711
  31. package/src/service/types.ts +0 -219
  32. package/src/service/work-item-create.ts +0 -159
  33. package/src/service/workspace-profile.ts +0 -443
  34. package/src/store/backup.ts +0 -239
  35. package/src/store/bootstrap.ts +0 -225
  36. package/src/store/db.ts +0 -179
  37. package/src/store/lock.ts +0 -194
  38. package/src/store/migrations.ts +0 -211
  39. package/src/store/schema/001_p1.sql +0 -846
  40. package/src/store/schema/001_p1.sql.ts +0 -865
  41. package/src/store/schema/002_knowledge.sql +0 -54
  42. package/src/store/schema/002_knowledge.sql.ts +0 -68
  43. package/src/store/schema/003_projects.sql.ts +0 -3
  44. package/src/store/schema/004_team.sql.ts +0 -50
  45. package/src/store/schema/005_target_check.sql.ts +0 -22
  46. package/src/store/schema/006_target_flow.sql.ts +0 -19
  47. package/src/store/schema/007_project_delete.sql.ts +0 -17
  48. package/src/store/schema/008_memory.sql.ts +0 -47
  49. package/src/store/schema/generate-projects.mjs +0 -16
  50. package/src/store/seed.ts +0 -114
@@ -1,219 +0,0 @@
1
- /**
2
- * 服务层共享类型:命令上下文与回执。
3
- *
4
- * 两条边界:
5
- * - `principalId` 只能由进站适配器从服务端上下文解析后传入,**绝不读请求体**(C5 结论);
6
- * - 业务成败写在回执的 `ok` / `status` 上,HTTP 状态码只表达传输层(backend-design §9)。
7
- */
8
- import type { HamsErrorCode } from '../domain/errors.ts'
9
-
10
- export type Channel = 'ui' | 'tool' | 'system' | 'connector'
11
- export type MemberKind = 'human' | 'bot' | 'system'
12
-
13
- export interface CommandContext {
14
- /** 服务端解析出来的操作者;不接受客户端自报。 */
15
- principalId: string
16
- channel: Channel
17
- correlationId: string
18
- /** 写命令的幂等键;同一 principal + action + key 只产生一次业务效果。 */
19
- idempotencyKey?: string
20
- expectedRevision?: number
21
- /** 代录:实际操作人(P1 单机模式下他人参与的留痕方式)。 */
22
- onBehalfOf?: string
23
- authSubject?: string
24
- hostPreset?: string
25
- }
26
-
27
- export interface CommandReceipt {
28
- ok: boolean
29
- status: 'applied' | 'accepted' | 'rejected'
30
- objectId?: string
31
- objectRevision?: number
32
- executionEpoch?: number
33
- approvalId?: string
34
- auditId: string
35
- correlationId: string
36
- detail?: Record<string, unknown>
37
- error?: {
38
- code: HamsErrorCode
39
- message: string
40
- nextStep?: string
41
- detail?: unknown
42
- }
43
- }
44
-
45
- export interface Principal {
46
- memberId: string
47
- kind: MemberKind
48
- name: string
49
- role: string
50
- status: string
51
- currentVersion: number
52
- positions: { positionId: string; name: string }[]
53
- capabilities: Set<string>
54
- }
55
-
56
- export interface WorkItemRow {
57
- id: string
58
- project_id: string | null
59
- parent_id: string | null
60
- template_version_id: string | null
61
- asset_id: string | null
62
- domain: string
63
- category: string
64
- title: string
65
- priority: number
66
- status: string
67
- resume_state: string | null
68
- blocked_reason: string | null
69
- owner_human_id: string
70
- assignee_id: string | null
71
- due_at: number | null
72
- next_action_at: number | null
73
- check_generation: number
74
- close_requires_approval: number
75
- dedupe_key: string | null
76
- revision: number
77
- execution_epoch: number
78
- fence_seq: number
79
- evidence_set_hash: string
80
- created_by: string
81
- created_at: number
82
- updated_at: number
83
- completed_at: number | null
84
- }
85
-
86
- export interface ApprovalRow {
87
- id: string
88
- subject_kind: string
89
- subject_id: string
90
- subject_revision: number
91
- execution_epoch: number
92
- evidence_set_hash: string
93
- criteria_ref: string
94
- criteria_json: string
95
- reason_text: string
96
- requested_by: string
97
- requested_channel: string
98
- approver_id: string
99
- status: string
100
- decided_by: string | null
101
- decided_channel: string | null
102
- decision_note: string
103
- decided_at: number | null
104
- invalidated_reason: string | null
105
- expires_at: number
106
- created_at: number
107
- }
108
-
109
- export interface CheckRow {
110
- id: string
111
- work_item_id: string
112
- generation: number
113
- execution_epoch: number
114
- template_version_id: string | null
115
- planned_due_at: number
116
- status: string
117
- evidence_set_hash: string
118
- result_json: string
119
- evaluated_at: number | null
120
- created_at: number
121
- }
122
-
123
- export interface TemplateRow {
124
- id: string
125
- key: string
126
- name: string
127
- domain: string
128
- category: string
129
- published_version_id: string | null
130
- }
131
-
132
- export interface TemplateVersionRow {
133
- id: string
134
- template_id: string
135
- version: number
136
- required_evidence_json: string
137
- validator_key: string | null
138
- validator_params_json: string
139
- check_policy_json: string
140
- review_policy_json: string
141
- misfire_policy: string
142
- status: string
143
- published_by: string | null
144
- published_at: number | null
145
- }
146
-
147
- export interface RunRow {
148
- id: string
149
- work_item_id: string
150
- thread_id: string
151
- check_id: string | null
152
- member_id: string
153
- member_version: number
154
- execution_epoch: number
155
- runtime_epoch: number
156
- fence: number
157
- trigger: string
158
- attempt: number
159
- status: string
160
- result_summary: string | null
161
- created_at: number
162
- started_at: number | null
163
- finished_at: number | null
164
- }
165
-
166
- export interface ThreadRow {
167
- id: string
168
- work_item_id: string
169
- generation: number
170
- session_id: string
171
- member_id: string
172
- member_version: number
173
- status: string
174
- turn_count: number
175
- opened_at: number
176
- closed_at: number | null
177
- close_reason: string | null
178
- }
179
-
180
- export interface EvidenceRow {
181
- id: string
182
- work_item_id: string
183
- run_id: string | null
184
- kind: string
185
- ref: string
186
- content_hash: string
187
- source: string
188
- observed_at: number
189
- captured_at: number
190
- captured_by: string
191
- on_behalf_of: string | null
192
- supersedes_id: string | null
193
- status: string
194
- verified_by: string | null
195
- verified_at: number | null
196
- purged_at: number | null
197
- }
198
-
199
- export interface ProposalRow {
200
- id: string
201
- subject_key: string
202
- project_id: string | null
203
- work_item_id: string | null
204
- kind: string
205
- dedupe_key: string
206
- proposed_by: string
207
- decider_id: string
208
- title: string
209
- body: string
210
- rationale_json: string
211
- impact_json: string
212
- status: string
213
- duplicate_count: number
214
- snoozed_until: number | null
215
- expires_at: number | null
216
- result_work_item_id: string | null
217
- revision: number
218
- created_at: number
219
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * 建工作项与额度记账:命令层(告警入站 / 提议采纳)与调度(schedule 物化)共用这一份。
3
- *
4
- * 抽出来的原因:建单要同时满足五个不变量——负责人必须是人、模板必须已发布、
5
- * `dedupe_key` 未结单唯一、项目额度未超、事件与变更游标同事务。调度如果再抄一遍,
6
- * 迟早出现「人建的扣额度、定时建的不扣」这种账。
7
- */
8
- import type { DatabaseSync } from 'node:sqlite'
9
- import { denied, stateInvalid } from '../domain/errors.ts'
10
- import type { IdFactory } from '../domain/ids.ts'
11
- import { parseReviewPolicy } from '../domain/check.ts'
12
- import { writeChange, writeEvent } from './audit.ts'
13
- import type { MemberKind, TemplateVersionRow, WorkItemRow } from './types.ts'
14
-
15
- export interface WorkItemFactory {
16
- db: DatabaseSync
17
- ids: IdFactory
18
- now: () => number
19
- /** 预算按哪个时区切日/周。 */
20
- timeZone: string
21
- }
22
-
23
- export interface InsertWorkItemInput {
24
- domain: string
25
- category: string
26
- title: string
27
- ownerHumanId: string
28
- projectId?: string | null
29
- templateVersionId?: string | null
30
- assetId?: string | null
31
- dedupeKey?: string | null
32
- priority?: number
33
- dueAt?: number | null
34
- createdBy: string
35
- actorKind: MemberKind
36
- }
37
-
38
- export function insertWorkItem(factory: WorkItemFactory, input: InsertWorkItemInput): WorkItemRow {
39
- const { db } = factory
40
- const at = factory.now()
41
-
42
- const owner = db.prepare('SELECT kind FROM member WHERE id = ?').get(input.ownerHumanId) as
43
- | { kind: string }
44
- | undefined
45
- if (!owner) throw stateInvalid(`负责人不存在:${input.ownerHumanId}`)
46
- if (owner.kind !== 'human') throw denied('工作项负责人必须是人')
47
-
48
- let closeRequiresApproval = 1
49
- if (input.templateVersionId) {
50
- const template = db.prepare('SELECT * FROM work_template_version WHERE id = ?').get(input.templateVersionId) as
51
- | TemplateVersionRow
52
- | undefined
53
- if (!template) throw stateInvalid(`模板版本不存在:${input.templateVersionId}`)
54
- if (template.status !== 'published') throw stateInvalid(`模板版本未发布:${template.status}`)
55
- closeRequiresApproval = parseReviewPolicy(template.review_policy_json).autoClose ? 0 : 1
56
- }
57
-
58
- if (input.projectId) consumeBudget(factory, input.projectId, at)
59
-
60
- const workItemId = factory.ids()
61
- db.prepare(
62
- `INSERT INTO work_item
63
- (id, project_id, parent_id, template_version_id, asset_id, domain, category, title, priority, status,
64
- owner_human_id, assignee_id, due_at, next_action_at, check_generation, close_requires_approval,
65
- dedupe_key, revision, execution_epoch, fence_seq, evidence_set_hash, created_by, created_at, updated_at)
66
- VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, 'ready', ?, NULL, ?, NULL, 0, ?, ?, 1, 1, 0, '', ?, ?, ?)`,
67
- ).run(
68
- workItemId,
69
- input.projectId ?? null,
70
- input.templateVersionId ?? null,
71
- input.assetId ?? null,
72
- input.domain,
73
- input.category,
74
- input.title,
75
- input.priority ?? 0,
76
- input.ownerHumanId,
77
- input.dueAt ?? null,
78
- closeRequiresApproval,
79
- input.dedupeKey ?? null,
80
- input.createdBy,
81
- at,
82
- at,
83
- )
84
-
85
- const item = db.prepare('SELECT * FROM work_item WHERE id = ?').get(workItemId) as WorkItemRow
86
- writeEvent(db, factory.ids(), {
87
- workItemId,
88
- kind: 'work_item_created',
89
- actorId: input.createdBy,
90
- actorKind: input.actorKind,
91
- payload: {
92
- title: input.title,
93
- dedupeKey: input.dedupeKey ?? null,
94
- templateVersionId: input.templateVersionId ?? null,
95
- },
96
- revision: item.revision,
97
- executionEpoch: item.execution_epoch,
98
- at,
99
- })
100
- writeChange(db, {
101
- objectKind: 'work_item',
102
- objectId: workItemId,
103
- revision: item.revision,
104
- changeKind: 'created',
105
- at,
106
- })
107
- return item
108
- }
109
-
110
- /**
111
- * 建单额度:日与周是两行,各自滚动;没有额度行视为不限。
112
- * 条件更新 `used < limit_value` 保证并发下不会超卖(单写者其实已足够,但这样更直白)。
113
- */
114
- export function consumeBudget(factory: WorkItemFactory, projectId: string, at: number): void {
115
- const { db } = factory
116
- const { dayStart, weekStart } = periodStarts(factory.timeZone, at)
117
- for (const [kind, start] of [
118
- ['day', dayStart],
119
- ['week', weekStart],
120
- ] as const) {
121
- const row = db
122
- .prepare(
123
- `SELECT used, limit_value FROM project_budget
124
- WHERE project_id = ? AND period_kind = ? AND period_start = ? AND metric = 'new_tasks'`,
125
- )
126
- .get(projectId, kind, start) as { used: number; limit_value: number } | undefined
127
- if (!row) continue
128
- if (Number(row.used) >= Number(row.limit_value)) {
129
- throw denied(
130
- `项目「${kind === 'day' ? '日' : '周'}」建单额度已用尽(${row.used}/${row.limit_value})`,
131
- '调高额度或等下一个周期',
132
- )
133
- }
134
- const updated = db
135
- .prepare(
136
- `UPDATE project_budget SET used = used + 1
137
- WHERE project_id = ? AND period_kind = ? AND period_start = ? AND metric = 'new_tasks' AND used < limit_value`,
138
- )
139
- .run(projectId, kind, start)
140
- if (updated.changes !== 1) {
141
- throw denied(`项目「${kind === 'day' ? '日' : '周'}」建单额度竞争失败`, '刷新后重试')
142
- }
143
- }
144
- }
145
-
146
- export function periodStarts(timeZone: string, at: number): { dayStart: string; weekStart: string } {
147
- const parts = new Intl.DateTimeFormat('en-CA', {
148
- timeZone,
149
- year: 'numeric',
150
- month: '2-digit',
151
- day: '2-digit',
152
- }).formatToParts(new Date(at))
153
- const pick = (type: string): number => Number(parts.find((part) => part.type === type)?.value ?? '0')
154
- const date = new Date(Date.UTC(pick('year'), pick('month') - 1, pick('day')))
155
- const weekday = date.getUTCDay()
156
- const monday = new Date(date.getTime() - ((weekday + 6) % 7) * 86_400_000)
157
- const format = (value: Date): string => value.toISOString().slice(0, 10)
158
- return { dayStart: format(date), weekStart: format(monday) }
159
- }