@dsh-enhanced/personal-wiki 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/cordis.patch.yml +18 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +9 -0
- package/lib/index.js.map +1 -0
- package/lib/page.d.ts +12 -0
- package/lib/page.d.ts.map +1 -0
- package/lib/page.js +204 -0
- package/lib/page.js.map +1 -0
- package/lib/proposals.d.ts +85 -0
- package/lib/proposals.d.ts.map +1 -0
- package/lib/proposals.js +521 -0
- package/lib/proposals.js.map +1 -0
- package/lib/service.d.ts +90 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +217 -0
- package/lib/service.js.map +1 -0
- package/lib/sqlite.d.ts +8 -0
- package/lib/sqlite.d.ts.map +1 -0
- package/lib/sqlite.js +123 -0
- package/lib/sqlite.js.map +1 -0
- package/lib/tokenize.d.ts +4 -0
- package/lib/tokenize.d.ts.map +1 -0
- package/lib/tokenize.js +39 -0
- package/lib/tokenize.js.map +1 -0
- package/lib/tools.d.ts +8 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +217 -0
- package/lib/tools.js.map +1 -0
- package/lib/types.d.ts +143 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/vault.d.ts +53 -0
- package/lib/vault.d.ts.map +1 -0
- package/lib/vault.js +750 -0
- package/lib/vault.js.map +1 -0
- package/lib/version.d.ts +2 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +2 -0
- package/lib/version.js.map +1 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-enhanced contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @dsh-enhanced/personal-wiki
|
|
2
|
+
|
|
3
|
+
面向个人助理的 Markdown 知识库。页面是人可读、可备份的唯一真源;进程内目录、段落检索索引、反链与 lint 报告都可从页面重建。它保存研究、项目、人物、概念、来源与决策等长资料;每轮都可能有用的短事实和偏好应交给 `personal-memory`。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
本插件要求 `@dsh-enhanced/assistant-policy` 先提供 `ctx.assistantPolicy`。生产聊天审批还应组合可选的 `@dsh-enhanced/assistant-delivery`;Wiki 只依赖它的公开类型与 Cordis service,不把 Delivery 打进自己的发布包:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
dsh plugin --profile web add @dsh-enhanced/assistant-policy
|
|
11
|
+
dsh plugin --profile web add @dsh-enhanced/assistant-delivery
|
|
12
|
+
dsh plugin --profile web add @dsh-enhanced/personal-wiki
|
|
13
|
+
dsh --profile web --dump-config
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
默认页面位于 `$DSH_HOME/personal-wiki/vault/wiki/`,提案状态位于 `$DSH_HOME/personal-wiki/state.sqlite`。默认 policy 没有 allow 规则,因此所有模型操作都会 fail closed。按真实 preset 与绝对 workspace 放行所需服务和工具:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
- id: dsh-enhanced-assistant-policy
|
|
20
|
+
name: '@dsh-enhanced/assistant-policy'
|
|
21
|
+
config:
|
|
22
|
+
databasePath: !!js dshHomePath('assistant-policy/policy.sqlite')
|
|
23
|
+
rules:
|
|
24
|
+
- id: allow-primary-wiki-service
|
|
25
|
+
effect: allow
|
|
26
|
+
subject: { kind: agent, id: primary, workspace: /absolute/workspace }
|
|
27
|
+
actions: [lint, propose, read, rebuild, search]
|
|
28
|
+
resource: { kind: wiki, id: "*" }
|
|
29
|
+
context: { initiators: [foreground] }
|
|
30
|
+
- id: allow-primary-wiki-tools
|
|
31
|
+
effect: allow
|
|
32
|
+
subject: { kind: agent, id: primary, workspace: /absolute/workspace }
|
|
33
|
+
actions: [execute]
|
|
34
|
+
resource: { kind: tool, id: wiki_* }
|
|
35
|
+
context: { initiators: [foreground] }
|
|
36
|
+
budgets: []
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 工具和写入语义
|
|
40
|
+
|
|
41
|
+
- `wiki_search`:显式搜索页与段落,支持 ASCII 和中文 unigram/bigram,返回有界 snippet、稳定 page id、相对路径、revision 与直接来源。
|
|
42
|
+
- `wiki_read`:用 `wiki://<ULID>`、ULID、精确标题或别名读取一页;输出受字节/段落双预算约束,并以 `<knowledge_source>` 标记为“不可信数据而非指令”。标题和正文会先做 XML 文本转义,页面无法闭合该数据边界。
|
|
43
|
+
- `wiki_upsert`:只提交完整 create/update 提案。update 必须带 `page_id` 与 `expected_revision`;模型没有直接写页的方法,也不能提供 principal、workspace、飞书 binding、审批 TTL 或页面 authority。TTL 与 authority 分别由可信配置 `defaultProposalTtlMs`、`toolProposalAuthority` 决定。
|
|
44
|
+
- `wiki_lint`:只读报告坏 frontmatter、重复 id/标题/别名、大小写路径碰撞、死链、孤页、来源哈希、派生 provenance 和进程索引漂移;不会修复或重建。
|
|
45
|
+
|
|
46
|
+
`wiki_search`、`wiki_upsert` 和 `wiki_lint` 的模型可见结果也分别放在有界、XML 转义的 `<wiki_search_results>`、`<wiki_proposal_results>`、`<wiki_lint_results>` 数据 framing 中。
|
|
47
|
+
|
|
48
|
+
插件还注册一个短的 `personal-wiki-workflow` Skill,要求 search/read 后再提案、保留 `wiki://` 引用、把来源视为数据,并禁止绕过审批直接用 shell 改 vault。
|
|
49
|
+
|
|
50
|
+
`wiki_upsert` 会从当前 Agent 的 Delivery owner binding 派生 exact principal、workspace 和 binding,并让 Policy 在创建提案的同一事务中持久化审批投递。模型无法改写该路由。没有 Delivery 的可信 headless 宿主仍可通过 `ctx.personalWiki.propose(..., { principal })` 显式指定 principal;模型工具没有这个兼容入口,缺少路由时会 fail closed。
|
|
51
|
+
|
|
52
|
+
`ctx.personalWiki.decideProposal(...)` 面向可信通道/UI。提案绑定 principal、TTL、版本和完整目标 Markdown。direct decide 与重启 reconcile 都使用 Policy 的共享 settlement validator,逐项核对 proposal id、requester、principal、action、resource、summary、expiry、version、decider 和本地 exact diff 的 SHA-256;任一不一致会持久化为 `conflicted`,绝不写 vault。批准 update 时还会重新读取 SHA-256 revision;外部编辑同样令提案进入 `conflicted`,不会覆盖新内容。policy 与 Wiki 状态分别持久化;Wiki 会先保存含 absolute deadline 和 exact dispatch 的恢复意图,再调用 Policy 的单事务 recover-or-create。Policy 会优先返回已存在的 exact proposal;若 deadline 已过且 proposal 不存在,则永久 tombstone 该 idempotency key,阻止另一个进程随后创建孤儿审批或续期。即使 Policy proposal 已提交、投递或批准而本地 attachment 尚未提交,重启后的 reconcile 也能找回同一 proposal,无需原 Agent 再提一次。只读 scoped lookup 仅用于诊断,不参与创建决策。
|
|
53
|
+
|
|
54
|
+
## 页面与重建
|
|
55
|
+
|
|
56
|
+
页面只位于固定目录 `wiki/{sources,people,projects,concepts,decisions,questions,meta}`。严格 JSON-compatible YAML frontmatter 包含 ULID、标题、类型、状态、authority、tags、aliases、直接 sources、创建/更新时间;未知字段和坏 SHA-256 会明确报错。`curated` 页面是独立真源;`derived` 页面必须指向直接 curated evidence,禁止 derived page 再总结 derived page。
|
|
57
|
+
|
|
58
|
+
启动时构建不可变进程快照。人工编辑后需显式 `rebuild` 才进入 search/read;失败的 rebuild 保留上一个可用快照。页面写入使用同目录 `0600` 临时文件、文件 fsync、atomic rename 和目录 fsync。页面目录为 `0700`,拒绝相对 root、`..`、绝对子路径、symlink、非普通文件、越界 realpath、大小写碰撞和超限页面。
|
|
59
|
+
|
|
60
|
+
备份时以整个 `vault/wiki` 为准;`state.sqlite` 只需在还要保留未决审批时一并备份。删除进程索引无需恢复,因为它只存在内存中。
|
|
61
|
+
|
|
62
|
+
## 配置上限
|
|
63
|
+
|
|
64
|
+
| 字段 | 默认值 | 作用 |
|
|
65
|
+
|---|---:|---|
|
|
66
|
+
| `maxPageBytes` | 1048576 | 单页 Markdown 最大 UTF-8 字节数 |
|
|
67
|
+
| `searchLimit` | 20 | 默认搜索命中上限(硬上限 100) |
|
|
68
|
+
| `maxSnippetBytes` | 2048 | 单条搜索片段字节上限 |
|
|
69
|
+
| `readMaxBytes` | 8192 | 单次读取总字节上限 |
|
|
70
|
+
| `readMaxParagraphs` | 40 | 单次读取段落上限 |
|
|
71
|
+
| `lintLimit` | 200 | 默认 lint finding 上限 |
|
|
72
|
+
| `defaultProposalTtlMs` | 900000 | 默认提案有效期(15 分钟) |
|
|
73
|
+
| `toolProposalAuthority` | `curated` | 可信部署为所有模型 `wiki_upsert` 固定的 `curated`/`derived` 分类;模型不可覆盖 |
|
|
74
|
+
| `reconcileIntervalMs` | 15000 | 提交“会话结束后才被批准”的提案的轮询间隔;`0` 关闭定时器 |
|
|
75
|
+
| `reconcileLimit` | 50 | 每轮 reconcile 检查的本地待决提案上限 |
|
|
76
|
+
|
|
77
|
+
## 延迟审批的提交闭环
|
|
78
|
+
|
|
79
|
+
`wiki_upsert` 只创建提案。审批可能发生在原 turn 结束之后(例如几分钟后在飞书卡片上点击),因此本插件会周期性调用 `reconcileProposals()`:读取 policy 账本中该提案的**终态**,再走与 `decideProposal` 相同的写入路径(同目录 temp + fsync + 原子 rename)。
|
|
80
|
+
|
|
81
|
+
五条边界:
|
|
82
|
+
|
|
83
|
+
- **不推断批准**:pending 仍然是 pending;只有 policy 账本给出终态才写 vault。
|
|
84
|
+
- **幂等**:重复 reconcile 不会重复写页面或重复结算。
|
|
85
|
+
- **公平扫描**:reconcile cursor 和每轮固定 high-water 都持久化;本轮开始后的新增提案留到下一轮,前面的长期 pending 不会饿死后面已批准的提案,重启也不会回到第一页。
|
|
86
|
+
- **不复活旧意图**:Policy 尚未提交且 absolute deadline 已过时,会在 Policy 同一写事务中 tombstone idempotency key,并把本地意图置为 `conflicted`;多进程竞争也不能随后生成孤儿卡片。
|
|
87
|
+
- **冲突不丢决定**:`expectedRevision` 已变化或 Policy settlement 的不可变字段不匹配时,提案持久化为 `conflicted`,而不是覆盖文件或反复重试。
|
|
88
|
+
|
|
89
|
+
需要完全由宿主驱动时,把 `reconcileIntervalMs` 设为 `0` 并自行调用 `reconcileProposals()`。
|
|
90
|
+
|
|
91
|
+
## 权限与非目标
|
|
92
|
+
|
|
93
|
+
- 文件系统:只读写显式 `vaultRoot`、`databasePath` 及 SQLite WAL/SHM;不会扫描工作区或任意绝对路径。
|
|
94
|
+
- 网络、子进程、凭据、浏览器、安装脚本:本插件自身无;组合 Delivery 时只提交不可变审批 dispatch,由 Delivery/Lark 各自声明并持有网络与凭据权限。
|
|
95
|
+
- Git:不执行 add/commit/push;用户可在 vault 外部自行纳入版本控制。
|
|
96
|
+
- 非目标:PDF/DOCX/web ingest、向量数据库、Web UI、自动摘要写入、自动 archive、自动 Git commit、远端/多 vault 同步、Memory 双向同步、图数据库和多用户 ACL。
|
|
97
|
+
|
|
98
|
+
## 兼容性
|
|
99
|
+
|
|
100
|
+
已针对 DeepSeek Harness `0.1.0-rc.8`(源码提交 `141eb6fef83422698aef7a981029e843e8161534`)验证。详见仓库[兼容性基线](../../docs/compatibility.md)。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
- insert:
|
|
2
|
+
- id: dsh-enhanced-personal-wiki
|
|
3
|
+
name: '@dsh-enhanced/personal-wiki'
|
|
4
|
+
inject:
|
|
5
|
+
- assistantPolicy
|
|
6
|
+
config:
|
|
7
|
+
vaultRoot: !!js dshHomePath('personal-wiki/vault')
|
|
8
|
+
databasePath: !!js dshHomePath('personal-wiki/state.sqlite')
|
|
9
|
+
maxPageBytes: 1048576
|
|
10
|
+
searchLimit: 20
|
|
11
|
+
maxSnippetBytes: 2048
|
|
12
|
+
readMaxBytes: 8192
|
|
13
|
+
readMaxParagraphs: 40
|
|
14
|
+
lintLimit: 200
|
|
15
|
+
defaultProposalTtlMs: 900000
|
|
16
|
+
toolProposalAuthority: curated
|
|
17
|
+
reconcileIntervalMs: 15000
|
|
18
|
+
reconcileLimit: 50
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { PersonalWikiService } from './service.js';
|
|
3
|
+
import type { Config } from './service.js';
|
|
4
|
+
import { version } from './version.js';
|
|
5
|
+
export declare const name = "dsh-enhanced-personal-wiki";
|
|
6
|
+
export { PersonalWikiService, version };
|
|
7
|
+
export type { Config };
|
|
8
|
+
export type * from './types.js';
|
|
9
|
+
export declare function apply(ctx: Context, config: import('./service.js').Config): void;
|
|
10
|
+
export default PersonalWikiService;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,eAAO,MAAM,IAAI,+BAA+B,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAA;AACvC,YAAY,EAAE,MAAM,EAAE,CAAA;AACtB,mBAAmB,YAAY,CAAA;AAE/B,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,GAAG,IAAI,CAE/E;AAED,eAAe,mBAAmB,CAAA"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PersonalWikiService } from './service.js';
|
|
2
|
+
import { version } from './version.js';
|
|
3
|
+
export const name = 'dsh-enhanced-personal-wiki';
|
|
4
|
+
export { PersonalWikiService, version };
|
|
5
|
+
export function apply(ctx, config) {
|
|
6
|
+
new PersonalWikiService(ctx, config);
|
|
7
|
+
}
|
|
8
|
+
export default PersonalWikiService;
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,MAAM,CAAC,MAAM,IAAI,GAAG,4BAA4B,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAA;AAIvC,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAqC;IACvE,IAAI,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AACtC,CAAC;AAED,eAAe,mBAAmB,CAAA"}
|
package/lib/page.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WikiPage, WikiPageInput, WikiPageMetadata } from './types.js';
|
|
2
|
+
export declare class WikiPageError extends Error {
|
|
3
|
+
readonly code: 'invalid-frontmatter' | 'invalid-page';
|
|
4
|
+
constructor(code: 'invalid-frontmatter' | 'invalid-page', message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function createPageId(now: number, entropy?: Uint8Array): string;
|
|
7
|
+
export declare function validatePageInput(input: WikiPageInput): WikiPageInput;
|
|
8
|
+
export declare function validateMetadata(value: unknown): WikiPageMetadata;
|
|
9
|
+
export declare function serializePage(metadata: WikiPageMetadata, body: string): string;
|
|
10
|
+
export declare function parsePage(markdown: string, relativePath: string): WikiPage;
|
|
11
|
+
export declare function pageSlug(title: string): string;
|
|
12
|
+
//# sourceMappingURL=page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EAER,aAAa,EACb,gBAAgB,EAIjB,MAAM,YAAY,CAAA;AAcnB,qBAAa,aAAc,SAAQ,KAAK;IAC1B,QAAQ,CAAC,IAAI,EAAE,qBAAqB,GAAG,cAAc;gBAA5C,IAAI,EAAE,qBAAqB,GAAG,cAAc,EAAE,OAAO,EAAE,MAAM;CAInF;AAYD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,UAA4B,GAAG,MAAM,CAQvF;AA8CD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CA2BrE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAuCjE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAe9E;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,QAAQ,CAoB1E;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM9C"}
|
package/lib/page.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
const ULID = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
3
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
4
|
+
const PAGE_TYPES = new Set([
|
|
5
|
+
'concept', 'decision', 'meta', 'person', 'project', 'question', 'source',
|
|
6
|
+
]);
|
|
7
|
+
const AUTHORITIES = new Set(['curated', 'derived']);
|
|
8
|
+
const STATUSES = new Set(['active', 'archived', 'draft']);
|
|
9
|
+
const FRONTMATTER_KEYS = new Set([
|
|
10
|
+
'aliases', 'authority', 'created', 'derivedFrom', 'id', 'sources', 'status', 'tags', 'title', 'type', 'updated',
|
|
11
|
+
]);
|
|
12
|
+
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
13
|
+
export class WikiPageError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
constructor(code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = 'WikiPageError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function encodeBase32(value, length) {
|
|
22
|
+
let remaining = value;
|
|
23
|
+
let output = '';
|
|
24
|
+
for (let index = 0; index < length; index += 1) {
|
|
25
|
+
output = CROCKFORD[Number(remaining & 31n)] + output;
|
|
26
|
+
remaining >>= 5n;
|
|
27
|
+
}
|
|
28
|
+
return output;
|
|
29
|
+
}
|
|
30
|
+
export function createPageId(now, entropy = randomBytes(10)) {
|
|
31
|
+
if (!Number.isSafeInteger(now) || now < 0 || now > 0xffff_ffff_ffff) {
|
|
32
|
+
throw new WikiPageError('invalid-page', 'page timestamp is outside the ULID range');
|
|
33
|
+
}
|
|
34
|
+
if (entropy.length !== 10)
|
|
35
|
+
throw new WikiPageError('invalid-page', 'page id entropy must be 10 bytes');
|
|
36
|
+
let random = 0n;
|
|
37
|
+
for (const byte of entropy)
|
|
38
|
+
random = (random << 8n) | BigInt(byte);
|
|
39
|
+
return encodeBase32(BigInt(now), 10) + encodeBase32(random, 16);
|
|
40
|
+
}
|
|
41
|
+
function text(value, field) {
|
|
42
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
43
|
+
throw new WikiPageError('invalid-page', `${field} must be a non-empty string`);
|
|
44
|
+
}
|
|
45
|
+
return value.normalize('NFC').trim();
|
|
46
|
+
}
|
|
47
|
+
function stringList(value, field) {
|
|
48
|
+
if (!Array.isArray(value))
|
|
49
|
+
throw new WikiPageError('invalid-page', `${field} must be an array`);
|
|
50
|
+
const normalized = value.map(item => text(item, field));
|
|
51
|
+
const folded = new Set();
|
|
52
|
+
for (const item of normalized) {
|
|
53
|
+
const key = item.normalize('NFKC').toLocaleLowerCase('en-US');
|
|
54
|
+
if (folded.has(key))
|
|
55
|
+
throw new WikiPageError('invalid-page', `${field} contains a duplicate`);
|
|
56
|
+
folded.add(key);
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
function sources(value) {
|
|
61
|
+
if (!Array.isArray(value))
|
|
62
|
+
throw new WikiPageError('invalid-page', 'sources must be an array');
|
|
63
|
+
return value.map((item) => {
|
|
64
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
|
|
65
|
+
throw new WikiPageError('invalid-page', 'source must be an object');
|
|
66
|
+
}
|
|
67
|
+
const source = item;
|
|
68
|
+
if (Object.keys(source).some(key => key !== 'sha256' && key !== 'uri')) {
|
|
69
|
+
throw new WikiPageError('invalid-page', 'source contains an unknown field');
|
|
70
|
+
}
|
|
71
|
+
const uri = text(source['uri'], 'source uri');
|
|
72
|
+
const sha256 = text(source['sha256'], 'source sha256').toLocaleLowerCase('en-US');
|
|
73
|
+
if (!SHA256.test(sha256))
|
|
74
|
+
throw new WikiPageError('invalid-page', 'source sha256 must be 64 lowercase hex');
|
|
75
|
+
return Object.freeze({ uri, sha256 });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function timestamp(value, field) {
|
|
79
|
+
const normalized = text(value, field);
|
|
80
|
+
if (!Number.isFinite(Date.parse(normalized)) || new Date(normalized).toISOString() !== normalized) {
|
|
81
|
+
throw new WikiPageError('invalid-page', `${field} must be a canonical ISO timestamp`);
|
|
82
|
+
}
|
|
83
|
+
return normalized;
|
|
84
|
+
}
|
|
85
|
+
export function validatePageInput(input) {
|
|
86
|
+
const title = text(input.title, 'title');
|
|
87
|
+
if (!PAGE_TYPES.has(input.type))
|
|
88
|
+
throw new WikiPageError('invalid-page', 'invalid page type');
|
|
89
|
+
if (!AUTHORITIES.has(input.authority))
|
|
90
|
+
throw new WikiPageError('invalid-page', 'invalid page authority');
|
|
91
|
+
if (!STATUSES.has(input.status))
|
|
92
|
+
throw new WikiPageError('invalid-page', 'invalid page status');
|
|
93
|
+
const tags = stringList(input.tags, 'tags');
|
|
94
|
+
const aliases = stringList(input.aliases, 'aliases');
|
|
95
|
+
const pageSources = sources(input.sources);
|
|
96
|
+
const derivedFrom = input.derivedFrom === undefined ? undefined : stringList(input.derivedFrom, 'derivedFrom');
|
|
97
|
+
if (derivedFrom?.some(id => !ULID.test(id))) {
|
|
98
|
+
throw new WikiPageError('invalid-page', 'derivedFrom must contain page ULIDs');
|
|
99
|
+
}
|
|
100
|
+
if (input.authority === 'derived' && pageSources.length === 0 && (derivedFrom?.length ?? 0) === 0) {
|
|
101
|
+
throw new WikiPageError('invalid-page', 'derived pages require direct source evidence');
|
|
102
|
+
}
|
|
103
|
+
if (typeof input.body !== 'string')
|
|
104
|
+
throw new WikiPageError('invalid-page', 'body must be a string');
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
title,
|
|
107
|
+
type: input.type,
|
|
108
|
+
authority: input.authority,
|
|
109
|
+
status: input.status,
|
|
110
|
+
tags: Object.freeze(tags),
|
|
111
|
+
aliases: Object.freeze(aliases),
|
|
112
|
+
sources: Object.freeze(pageSources),
|
|
113
|
+
...(derivedFrom === undefined ? {} : { derivedFrom: Object.freeze(derivedFrom) }),
|
|
114
|
+
body: input.body.normalize('NFC'),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export function validateMetadata(value) {
|
|
118
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
119
|
+
throw new WikiPageError('invalid-frontmatter', 'frontmatter must be an object');
|
|
120
|
+
}
|
|
121
|
+
const input = value;
|
|
122
|
+
if (Object.keys(input).some(key => !FRONTMATTER_KEYS.has(key))) {
|
|
123
|
+
throw new WikiPageError('invalid-frontmatter', 'frontmatter contains an unknown field');
|
|
124
|
+
}
|
|
125
|
+
const id = text(input['id'], 'id');
|
|
126
|
+
if (!ULID.test(id))
|
|
127
|
+
throw new WikiPageError('invalid-page', 'page id must be a ULID');
|
|
128
|
+
const created = timestamp(input['created'], 'created');
|
|
129
|
+
const updated = timestamp(input['updated'], 'updated');
|
|
130
|
+
if (Date.parse(updated) < Date.parse(created)) {
|
|
131
|
+
throw new WikiPageError('invalid-page', 'page updated timestamp precedes created');
|
|
132
|
+
}
|
|
133
|
+
const page = validatePageInput({
|
|
134
|
+
title: input['title'],
|
|
135
|
+
type: input['type'],
|
|
136
|
+
authority: input['authority'],
|
|
137
|
+
status: input['status'],
|
|
138
|
+
tags: input['tags'],
|
|
139
|
+
aliases: input['aliases'],
|
|
140
|
+
sources: input['sources'],
|
|
141
|
+
...(input['derivedFrom'] === undefined ? {} : { derivedFrom: input['derivedFrom'] }),
|
|
142
|
+
body: '',
|
|
143
|
+
});
|
|
144
|
+
return Object.freeze({
|
|
145
|
+
id,
|
|
146
|
+
title: page.title,
|
|
147
|
+
type: page.type,
|
|
148
|
+
authority: page.authority,
|
|
149
|
+
status: page.status,
|
|
150
|
+
tags: page.tags,
|
|
151
|
+
aliases: page.aliases,
|
|
152
|
+
sources: page.sources,
|
|
153
|
+
...(page.derivedFrom === undefined ? {} : { derivedFrom: page.derivedFrom }),
|
|
154
|
+
created,
|
|
155
|
+
updated,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
export function serializePage(metadata, body) {
|
|
159
|
+
const document = {
|
|
160
|
+
id: metadata.id,
|
|
161
|
+
title: metadata.title,
|
|
162
|
+
type: metadata.type,
|
|
163
|
+
authority: metadata.authority,
|
|
164
|
+
status: metadata.status,
|
|
165
|
+
tags: metadata.tags,
|
|
166
|
+
aliases: metadata.aliases,
|
|
167
|
+
sources: metadata.sources,
|
|
168
|
+
...(metadata.derivedFrom === undefined ? {} : { derivedFrom: metadata.derivedFrom }),
|
|
169
|
+
created: metadata.created,
|
|
170
|
+
updated: metadata.updated,
|
|
171
|
+
};
|
|
172
|
+
return `---\n${JSON.stringify(document, null, 2)}\n---\n${body}`;
|
|
173
|
+
}
|
|
174
|
+
export function parsePage(markdown, relativePath) {
|
|
175
|
+
if (!markdown.startsWith('---\n')) {
|
|
176
|
+
throw new WikiPageError('invalid-frontmatter', 'page must start with frontmatter');
|
|
177
|
+
}
|
|
178
|
+
const boundary = markdown.indexOf('\n---\n', 4);
|
|
179
|
+
if (boundary < 0)
|
|
180
|
+
throw new WikiPageError('invalid-frontmatter', 'page frontmatter is not closed');
|
|
181
|
+
let decoded;
|
|
182
|
+
try {
|
|
183
|
+
decoded = JSON.parse(markdown.slice(4, boundary));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
throw new WikiPageError('invalid-frontmatter', `page frontmatter is not valid JSON-compatible YAML: ${String(error)}`);
|
|
187
|
+
}
|
|
188
|
+
const metadata = validateMetadata(decoded);
|
|
189
|
+
const body = markdown.slice(boundary + 5).normalize('NFC');
|
|
190
|
+
return Object.freeze({
|
|
191
|
+
metadata,
|
|
192
|
+
body,
|
|
193
|
+
relativePath,
|
|
194
|
+
revision: createHash('sha256').update(markdown).digest('hex'),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
export function pageSlug(title) {
|
|
198
|
+
const slug = title.normalize('NFKD').toLocaleLowerCase('en-US')
|
|
199
|
+
.replaceAll(/[^\p{Letter}\p{Number}]+/gu, '-')
|
|
200
|
+
.replaceAll(/^-|-$/g, '')
|
|
201
|
+
.slice(0, 60);
|
|
202
|
+
return slug === '' ? 'page' : slug;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=page.js.map
|
package/lib/page.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ApprovalDecisionInput, ApprovalDispatchRoute, ApprovalProposalRecoveryInput, ApprovalProposalRecoveryResult, ApprovalProposalResult, ApprovalProposalSnapshot } from '@dsh-enhanced/assistant-policy';
|
|
2
|
+
import type { PreparedWikiWrite, StoredWikiProposal, WikiProposalDecisionInput, WikiProposalInput, WikiProposalResult, WikiProposalStatus } from './types.js';
|
|
3
|
+
import { WikiVault } from './vault.js';
|
|
4
|
+
export type WikiProposalStoreErrorCode = 'idempotency-conflict' | 'invalid-input' | 'invalid-path' | 'invalid-state' | 'not-found' | 'schema-too-new';
|
|
5
|
+
export declare class WikiProposalStoreError extends Error {
|
|
6
|
+
readonly code: WikiProposalStoreErrorCode;
|
|
7
|
+
constructor(code: WikiProposalStoreErrorCode, message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface WikiApprovalPolicy {
|
|
10
|
+
recoverOrCreateProposal(input: ApprovalProposalRecoveryInput): ApprovalProposalRecoveryResult;
|
|
11
|
+
decideProposal(input: ApprovalDecisionInput): ApprovalProposalResult;
|
|
12
|
+
getProposal(proposalId: string): ApprovalProposalSnapshot | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare class WikiProposalStore {
|
|
15
|
+
private readonly database;
|
|
16
|
+
private readonly now;
|
|
17
|
+
private closed;
|
|
18
|
+
constructor(options: {
|
|
19
|
+
path: string;
|
|
20
|
+
now?: () => number;
|
|
21
|
+
});
|
|
22
|
+
close(): void;
|
|
23
|
+
get(id: string): StoredWikiProposal | undefined;
|
|
24
|
+
health(): {
|
|
25
|
+
pendingProposals: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Return one page from a durable high-water cycle. Newer rows are held for the
|
|
29
|
+
* next cycle, so a continuous arrival stream cannot prevent the cursor from
|
|
30
|
+
* wrapping to an older proposal that became terminal after its first scan.
|
|
31
|
+
*/
|
|
32
|
+
listPending(limit: number): StoredWikiProposal[];
|
|
33
|
+
prepare(input: {
|
|
34
|
+
proposalId: string;
|
|
35
|
+
idempotencyKey: string;
|
|
36
|
+
requester: string;
|
|
37
|
+
principal: string;
|
|
38
|
+
requestHash: string;
|
|
39
|
+
write: PreparedWikiWrite;
|
|
40
|
+
expiresAt: number;
|
|
41
|
+
ttlMs: number;
|
|
42
|
+
dispatch?: Readonly<ApprovalDispatchRoute>;
|
|
43
|
+
}): {
|
|
44
|
+
proposal: StoredWikiProposal;
|
|
45
|
+
replayed: boolean;
|
|
46
|
+
};
|
|
47
|
+
attachPolicy(id: string, policyProposalId: string, expiresAt: number): StoredWikiProposal;
|
|
48
|
+
settle(input: {
|
|
49
|
+
proposalId: string;
|
|
50
|
+
status: Exclude<WikiProposalStatus, 'pending'>;
|
|
51
|
+
version: number;
|
|
52
|
+
resultPageId?: string;
|
|
53
|
+
}): {
|
|
54
|
+
proposal: StoredWikiProposal;
|
|
55
|
+
replayed: boolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export declare class WikiProposalManager {
|
|
59
|
+
private readonly vault;
|
|
60
|
+
private readonly store;
|
|
61
|
+
private readonly policy;
|
|
62
|
+
private readonly now;
|
|
63
|
+
constructor(vault: WikiVault, store: WikiProposalStore, policy: WikiApprovalPolicy, now?: () => number);
|
|
64
|
+
propose(input: WikiProposalInput): WikiProposalResult;
|
|
65
|
+
decide(input: WikiProposalDecisionInput): WikiProposalResult;
|
|
66
|
+
/**
|
|
67
|
+
* Commit locally pending proposals whose policy decision already settled
|
|
68
|
+
* elsewhere, for example on an approval card minutes after the turn ended.
|
|
69
|
+
*
|
|
70
|
+
* Approval is never inferred: the terminal status is read back from the policy
|
|
71
|
+
* ledger and applied through the same settle path `decide()` uses, so repeated
|
|
72
|
+
* reconciles are idempotent.
|
|
73
|
+
*/
|
|
74
|
+
reconcile(limit: number): WikiProposalResult[];
|
|
75
|
+
/**
|
|
76
|
+
* Apply one already-terminal policy status to a pending wiki proposal. A vault
|
|
77
|
+
* conflict downgrades the proposal to `conflicted` instead of losing the
|
|
78
|
+
* decision, matching the durability rule that a decision is never re-asked.
|
|
79
|
+
*/
|
|
80
|
+
private settleDecided;
|
|
81
|
+
private settleSnapshot;
|
|
82
|
+
private settleSecurityConflict;
|
|
83
|
+
getProposal(id: string): WikiProposalResult | undefined;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=proposals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposals.d.ts","sourceRoot":"","sources":["../src/proposals.ts"],"names":[],"mappings":"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"}
|