dsh-hams 0.1.2 → 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.
- package/README.md +4 -0
- package/dist/index.js +11335 -0
- package/package.json +9 -5
- package/src/domain/check.ts +0 -193
- package/src/domain/errors.ts +0 -59
- package/src/domain/evidence.ts +0 -134
- package/src/domain/ids.ts +0 -31
- package/src/host/chat-runtime.ts +0 -489
- package/src/host/dsh-adapter.ts +0 -867
- package/src/host/session-adapter.ts +0 -276
- package/src/http/handler.ts +0 -1132
- package/src/index.js +0 -53
- package/src/service/audit.ts +0 -92
- package/src/service/chat.ts +0 -1100
- package/src/service/contracts.ts +0 -193
- package/src/service/defaults.ts +0 -182
- package/src/service/deliverables.ts +0 -240
- package/src/service/hams.ts +0 -3103
- package/src/service/knowledge.ts +0 -940
- package/src/service/member-config.ts +0 -39
- package/src/service/members.ts +0 -506
- package/src/service/memory.ts +0 -375
- package/src/service/permissions.ts +0 -118
- package/src/service/project-lifecycle.ts +0 -233
- package/src/service/projects.ts +0 -840
- package/src/service/receipts.ts +0 -63
- package/src/service/templates.ts +0 -140
- package/src/service/tick.ts +0 -751
- package/src/service/tools.ts +0 -711
- package/src/service/types.ts +0 -219
- package/src/service/work-item-create.ts +0 -159
- package/src/service/workspace-profile.ts +0 -443
- package/src/store/backup.ts +0 -239
- package/src/store/bootstrap.ts +0 -225
- package/src/store/db.ts +0 -179
- package/src/store/lock.ts +0 -194
- package/src/store/migrations.ts +0 -211
- package/src/store/schema/001_p1.sql +0 -846
- package/src/store/schema/001_p1.sql.ts +0 -865
- package/src/store/schema/002_knowledge.sql +0 -54
- package/src/store/schema/002_knowledge.sql.ts +0 -68
- package/src/store/schema/003_projects.sql.ts +0 -3
- package/src/store/schema/004_team.sql.ts +0 -50
- package/src/store/schema/005_target_check.sql.ts +0 -22
- package/src/store/schema/006_target_flow.sql.ts +0 -19
- package/src/store/schema/007_project_delete.sql.ts +0 -17
- package/src/store/schema/008_memory.sql.ts +0 -47
- package/src/store/schema/generate-projects.mjs +0 -16
- package/src/store/seed.ts +0 -114
package/src/service/types.ts
DELETED
|
@@ -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
|
-
}
|