Skip to content

Commit d3952cc

Browse files
committed
fix: verify documentation sources and optimize stable pagination
Align preview installation and navigation with current capabilities. Bind verified VextJS exports to committed document bytes, gate all topics in CI, and add a fresh installed business consumer. Reuse bounded private body snapshots only after strong source revalidation. Preserve full scans for mutable or unverifiable sources, document limits, and exercise drift, cancellation and fallback behavior.
1 parent 163ceb8 commit d3952cc

41 files changed

Lines changed: 1091 additions & 95 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/vextjs-ci.yml‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
node scripts/validation/vextjs-file-identity.mjs "$VEXT_ROOT" > "$CG_ARTIFACTS_DIR/verification/vextjs-build-diagnostic.json"
7474
exit 1
7575
fi
76-
- name: Verify native catalog, retrieval and actual TCP Sessions
76+
- name: Verify official documentation, native catalog, retrieval and actual TCP Sessions
7777
shell: bash
7878
run: |
7979
set -euo pipefail
@@ -85,3 +85,48 @@ jobs:
8585
name: vextjs-${{ matrix.os }}-node-${{ matrix.node }}
8686
path: ${{ steps.paths.outputs.verification }}
8787
retention-days: 7
88+
89+
installed-business-consumer:
90+
needs: native-and-session
91+
runs-on: ubuntu-latest
92+
timeout-minutes: 20
93+
env:
94+
VEXT_SOURCE_COMMIT: ef926649926e17543562b8169982fa1786285ecf
95+
VEXT_ROOT: ${{ github.workspace }}/../capability-graph-artifacts/sources/vextjs-consumer
96+
CG_ARTIFACTS_DIR: ${{ github.workspace }}/../capability-graph-artifacts
97+
steps:
98+
- uses: actions/checkout@v7
99+
with:
100+
persist-credentials: false
101+
- uses: actions/setup-node@v7
102+
with:
103+
node-version: '24.19.0'
104+
cache: npm
105+
- run: npm ci
106+
- run: npm run build && npm run build:tests
107+
- name: Build the fixed upstream source outside the project
108+
shell: bash
109+
run: |
110+
set -euo pipefail
111+
mkdir -p "$VEXT_ROOT"
112+
git -C "$VEXT_ROOT" init --quiet
113+
git -C "$VEXT_ROOT" remote add origin https://github.com/devcodex-labs/vextjs.git
114+
git -C "$VEXT_ROOT" fetch --depth=1 origin "$VEXT_SOURCE_COMMIT"
115+
git -C "$VEXT_ROOT" checkout --detach FETCH_HEAD
116+
test "$(git -C "$VEXT_ROOT" rev-parse HEAD)" = "$VEXT_SOURCE_COMMIT"
117+
npm ci --ignore-scripts --prefix "$VEXT_ROOT"
118+
npm --prefix "$VEXT_ROOT" run build
119+
- name: Install actual packages and verify documentation, HTTPS and MongoDB business requests
120+
shell: bash
121+
run: |
122+
set -euo pipefail
123+
docker pull mongo:8.0
124+
node scripts/validation/vextjs-consumer.mjs "$VEXT_ROOT" "$VEXT_SOURCE_COMMIT" --https
125+
- uses: actions/upload-artifact@v4
126+
if: always()
127+
with:
128+
name: vextjs-installed-business-consumer
129+
path: |
130+
${{ env.CG_ARTIFACTS_DIR }}/vextjs-e2e-*/reports
131+
${{ env.CG_ARTIFACTS_DIR }}/vextjs-e2e-*/logs
132+
retention-days: 7

‎README.md‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ Capability Graph 为 Provider 自有的 API、MCP 等接入提供协议无关的
2323
Registry 当前已发布 `1.0.1`;仓库为待发布 `1.1.0`。新目录模式、知识根别名及正文分页需使用本仓库构建的安装包;提交源码不会发布 npm 包或部署站点。主包只有 ESM 根入口,固定依赖 BCP 47 解析器与 IANA 注册表数据;MCP 示例为独立私有包,不导出 `./mcp`。文档源码位于 [website](website/)。
2424

2525
```sh
26-
npm install @devcodex/capability-graph
26+
npm install @devcodex/capability-graph@1.0.1
2727
```
2828

29-
首次接入从[安装](https://devcodex-labs.github.io/capability-graph/getting-started/installation)和[最小 Provider](https://devcodex-labs.github.io/capability-graph/getting-started/first-provider)开始,不需要克隆本仓库或配置知识、Runtime。关系、知识、必要上下文与规范按[进阶教程](https://devcodex-labs.github.io/capability-graph/guides/progressive-discovery)分步接入。下文的完整 Seed 展示增强合同,不是首次接入必须填写的配置。
29+
上面的 Registry 命令安装已发布 1.0.1。使用下文的目录模式、知识根和分页功能,请先按[当前预览版安装说明](website/docs/getting-started/installation.mdx)构建并安装 1.1.0。本仓库教程与[已部署站点](https://devcodex-labs.github.io/capability-graph/)可能处于不同版本;首次接入按[最小 Provider](website/docs/getting-started/first-provider.mdx)开始,再按[进阶教程](website/docs/guides/progressive-discovery.mdx)增强。
3030

3131
已实现文件权威加载、校验、单 Provider 正式图、跨 Provider 联合目录、范围控制、修订快照、按需知识读取,以及可插拔的数据库、检索和 Runtime 合同。真实 Seed 同时提供普通 API 与 MCP 接入。
3232

@@ -103,10 +103,13 @@ directory 只收集 capabilities/**/*.json,缺少目录、目录外遗留定
103103
| `getCapabilities` / `getNeighbors` / `listKnowledgeMembers` | 有界详情、八种关系和 Collection 成员发现 |
104104
| `resolveSelection` | 沿 `requires` 求完整必要上下文闭包,返回边与直接原因 |
105105
| `readDocuments` / `readSpecification` | 按用途/语言显式读取能力文档或 Provider 规范,逐项返回结果 |
106+
| `readDocumentPage` / `readSpecificationPage` | 按 UTF-8 字节分页读取正文,保留全文身份与续读游标 |
106107
| `retrieveCapabilities` | 显式调用已配置召回后端,Core 校验候选身份与修订 |
107108
| `queryKnowledge` | 对已选知识执行检索,校验证据、内容版本与片段边界 |
108109
| `queryRuntime` | 查询单 Provider、指定项目和环境下的运行实例 |
109110

111+
大文档使用 `readDocumentPage` / `readSpecificationPage`,模型决定范围与是否续读。默认 32 KiB 是页大小,文档总大小不设准入门槛;全文与本页哈希分开,内容变化拒绝旧游标。旧全文接口保持完整返回语义,超过单次响应容量时改用分页。
112+
110113
已知身份可直接查详情或读取,不强制从目录开始。第一轮目录只含能力摘要;显式选择由调用者决定,Core 只沿 `requires` 补齐必要上下文。分页必须保留过滤条件及修订;检查 `meta.completeness`、`warnings` 和 `nextCursor`,不能把部分结果当成全集。批量结果逐项检查 `ok`。
111114

112115
`getProvider` 保留摘要顶层字段,同时返回 `meta`,默认调用与绑定调用都能查看 `servedFrom` 和 `refreshFailed`。来源只在实际读取时检查;无关 Provider 故障不阻断独立查询。未指定修订的混合详情/文档请求保留正常槽位与各项错误,指定修订失效仍明确失败。
@@ -135,6 +138,8 @@ directory 只收集 capabilities/**/*.json,缺少目录、目录外遗留定
135138
- Adapter 在调用进程内运行,属于显式信任代码;scope 不是进程沙箱。相对知识路径受根目录及真实路径约束,远程读取策略归已配置 Reader。
136139
- [API 示例](examples/seed-api/README.md)与 [MCP 示例](examples/seed-mcp/README.md)共享 Core。工具命名、认证、宿主意图判断及规范采用归接入方,不属于主包。
137140

141+
官方文档导出、原生 MCP 审计与真实验证入口见 [VextJS 示例](examples/vextjs/README.md)。文档 Provider 的默认入口按官方主题发现;原生目录与运行快照另行审计。
142+
138143
<a id="development"></a>
139144

140145
## 本地开发
@@ -168,14 +173,12 @@ npm run evaluate
168173

169174
`evaluate` 使用明确期望的 Seed 任务记录正确性、遗漏、UTF-8 返回字节、调用数和本机耗时,不调用模型、不推断真实 Agent 准确率或节省比例。未配置检索后端的质量对照不适用。
170175

176+
CI 的 `test:package:built`、`evaluate:built` 和 website `build:built` 先核对源码及全部输出指纹;普通命令仍从当前源构建。共享 dist 的构建必须串行,临时报告、服务数据和浏览器输出在仓库外。
177+
178+
VextJS CI 分别验证固定来源的官方文档、原生 MCP 和 Session;另一个全新安装项目执行业务验收。独立文档入口:`node scripts/validation/vextjs-documentation.mjs <已构建的固定框架目录> <完整 commit>`,先执行根构建与测试编译。
179+
171180
<a id="license"></a>
172181

173182
## 许可证
174183

175184
Apache-2.0
176-
177-
大文档使用 `readDocumentPage` / `readSpecificationPage`,模型决定范围与是否续读。默认 32 KiB 是页大小,文档总大小不设准入门槛;全文与本页哈希分开,内容变化拒绝旧游标。旧全文接口保持完整返回语义,超过单次响应容量时改用分页。
178-
179-
VextJS 的固定原生 schema 2 接入与实际验证入口见 [examples/vextjs](examples/vextjs/README.md)。原生目录角色与来源保留,运行快照保持 partial/unknown/unverified;默认回归与外部框架、数据库、浏览器证据分开。
180-
181-
CI 的 `test:package:built`、`evaluate:built` 和 website `build:built` 先核对源码及全部输出指纹;普通命令仍从当前源构建。共享 dist 的构建必须串行,临时报告、服务数据和浏览器输出在仓库外。

‎changelogs/1.1.0.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
- 显式 `definitionLayout: 'directory'` 收集 `capabilities/**/*.json`,不再要求 `.capability` 后缀。旧配置缺省 `legacy`;同记录和 locator 的迁移保留逻辑身份及 Static Revision。混用、嵌套 Provider、错误目录、链接定义、大小写冲突和重复身份明确失败。
66
- 每 Provider 的 `knowledgeRoots` 可绑定获准目录或实际安装包;`relative-file` 的 `root` 引用别名,支持 scoped/pnpm、中文和空格,保留真实路径边界及私有来源上下文。
77
- 正文分页不设文档总大小准入门槛;模型选择范围和是否续读。全文与分页保留原始 BOM/CRLF,MD/MDX MIME 一致,全文哈希与本页哈希分别表达。
8+
- KnowledgeReader 新增可选 isContentCurrent 强来源核验合同;正文续页可复用有界私有副本,HTTP 参考用条件 HEAD/强 ETag,减少重复完整扫描。普通文件、弱验证器及缓存未命中继续完整核验,内容变化拒绝旧游标。缓存每实例最多 4 MiB、16 条,文档不设大小准入限制。
89
- 外部检索即使不调用 access,也核验全部来源与命中范围,包括零命中。数据库 Catalog 与全部八类关系检查完整性,漏项和假空流明确失败。
910
- Runtime 保留白名单修订/上下文错误及安全动作,移除 Adapter 私有 message/details;期限或未知异常仍为 unavailable。
1011
- HTTP 与词法检索源码参考增加宿主代理 Agent、强 ETag 完整快照复用和受预算约束的流式 Top-K。缓存预算不限制文档大小。
1112
- VextJS 固定源/安装构建指纹、原生回读、官方章节映射、独立召回门槛与 MonSQLize 双 Provider 真实来源矩阵可复现。来源材料与作者关联分别溯源,文档验证不代表功能执行或 Agent 成功。
13+
- VextJS verified 导出绑定物理来源根、固定提交与逐篇 Git blob;CI 增加全部 91 篇官方文档、46 个主题的 63 条独立任务,以及全新安装项目业务验收。参考能力检索使用中文词组、常见否定处理和词项加权,仍是有限词法参考。
1214

1315
迁移:旧定义无需批量重命名;使用目录模式时迁移文件并同步配置,不混用旧目录外定义。新增接口不能在 Registry 1.0.1 上验证。外部知识索引需实际来源 Reader;数据库 Adapter 需完整支持八类关系。失效的索引/游标由宿主显式刷新和 invalidate/rebuild,Core 不自动更新后端。
1416

‎examples/seed-runtime/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
来源经代理访问时,宿主通过 `agentForUrl` 注入兼容 Node 20/22/24 的 Agent,例如根开发依赖中的 `proxy-agent`。Agent 由宿主管理和关闭,Reader 不接管它的生命周期;代理凭据不进入报告。
1212

13-
可选 `snapshot` 由宿主指定仓库外绝对目录,并配置 `maxBytes`、`maxEntries`、`ttlMs`。只有完整读取且带强 ETag 的正文才写入快照;后续条件请求返回 304 时仍核对本地完整正文的字节数和 SHA-256。弱/无验证器或正文超过缓存预算时继续完整流式读取;快照预算控制缓存保留,不限制可读取文档大小。Reader 的 `close()` 只清理它自己创建的快照子目录。
13+
可选 `snapshot` 由宿主指定仓库外绝对目录,并配置 `maxBytes`、`maxEntries`、`ttlMs`。只有完整读取且带强 ETag 的正文才写入快照;后续条件请求返回 304 时仍核对本地完整正文的字节数和 SHA-256。弱/无验证器或正文超过缓存预算时继续完整流式读取;快照预算控制缓存保留,不限制可读取文档大小。正文续页还支持可选 `isContentCurrent`:条件 HEAD 的 304 核验已绑定的强 ETag,Core 复用已完整哈希的私有字节副本,每实例最多 4 MiB、16 条,避免重复磁盘扫描和哈希。不支持 HEAD、弱验证器、过期或超缓存容量时重新完整核验。Reader 的 `close()` 只清理它自己创建的快照子目录。
1414

1515
`text-retrieval.ts` 是确定性的词项索引。`TextKnowledgeRetriever` 通过 `scan` 消费大正文,查询时保留有界 Top-K;完整索引仅在 `maxCachedBytes`、`maxCachedEntries` 及选择数预算内缓存,超过预算时继续流式检索。`configure({ pageBytes })` 控制扫描块大小,`maxDocumentBytes` 是兼容旧名称,两者都不表示全文准入上限;只提供 `read` 的旧 access 使用有界全文回退。Core 正文分页接口只保留所选范围,并计算全文哈希;模型决定是否继续读取。
1616

‎examples/seed-runtime/knowledge-reader.ts‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,44 @@ export class HttpKnowledgeReader implements KnowledgeReader {
4949
private async snapshotDirectory(): Promise<string> {
5050
return this.directory ??= (async () => { await mkdir(this.options.snapshot!.directory, { recursive: true }); return mkdtemp(path.join(this.options.snapshot!.directory, "cg-http-")); })();
5151
}
52-
/** Strong ETag validates a previously complete local snapshot; weak/no validator falls back to full GET.
53-
* No 206 is accepted: Core still hashes the complete source for every page/query. */
52+
/** Revalidate the exact representation already hashed by Core, without rereading the disk snapshot.
53+
* Conditional HEAD has no body. Unsupported HEAD or a changed validator requests a full stream. */
54+
async isContentCurrent(ref: KnowledgeDocumentRef, _context: KnowledgeReadContext, contentId: string, options: { signal?: AbortSignal }): Promise<boolean> {
55+
if (this.closed || ref.locator.type !== "http") throw new Error("Reader unavailable");
56+
const source = ref.locator.url; const url = new URL(source);
57+
if (!this.origins.has(url.origin) || url.username || url.password || this.streams.size >= this.maxConcurrent) throw new Error("Source unavailable");
58+
options.signal?.throwIfAborted();
59+
const snapshot = this.snapshots.get(source);
60+
if (!snapshot || snapshot.expires <= Date.now() || contentId !== `k:${snapshot.sha256.slice(0, 16)}`) return false;
61+
let finish!: () => void; const done = new Promise<void>((resolve) => { finish = resolve; }); this.streams.add(done);
62+
let request: ClientRequest | undefined; let timer: ReturnType<typeof setTimeout> | undefined;
63+
const abort = () => request?.destroy(new Error("HTTP knowledge verification aborted"));
64+
try {
65+
const response = await new Promise<IncomingMessage>((resolve, reject) => {
66+
request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
67+
method: "HEAD", agent: this.options.agentForUrl?.(url) ?? false,
68+
headers: { "accept-encoding": "identity", accept: "text/plain, text/markdown, application/json", "if-none-match": snapshot.etag },
69+
}, resolve);
70+
this.requests.add(request); request.on("error", reject); options.signal?.addEventListener("abort", abort, { once: true });
71+
timer = setTimeout(() => request?.destroy(new Error("HTTP knowledge deadline exceeded")), this.timeoutMs); request.end();
72+
});
73+
if (response.headers["content-encoding"] && response.headers["content-encoding"] !== "identity") throw new Error("Encoded source unsupported");
74+
response.resume(); await new Promise<void>((resolve, reject) => { response.once("end", resolve); response.once("error", reject); });
75+
options.signal?.throwIfAborted(); if (this.closed || !response.complete) throw new Error("Reader unavailable");
76+
if (response.statusCode === 304) {
77+
if (response.headers.etag !== undefined && response.headers.etag !== snapshot.etag) throw new Error("Unbound conditional response");
78+
return true;
79+
}
80+
if ([200, 405, 501].includes(response.statusCode ?? 0)) return false;
81+
throw new Error("Source unavailable");
82+
} finally {
83+
clearTimeout(timer); options.signal?.removeEventListener("abort", abort);
84+
if (request) { request.destroy(); if (!request.closed) await new Promise<void>((resolve) => request!.once("close", resolve)); this.requests.delete(request); }
85+
finish(); this.streams.delete(done);
86+
}
87+
}
88+
/** Strong ETag validates a previously complete disk snapshot; weak/no validator falls back to full GET.
89+
* Normal streams still verify complete bytes; Core may reuse its own proved copy for body pages. No 206 is accepted. */
5490
async *stream(ref: KnowledgeDocumentRef, _context: KnowledgeReadContext, options: { chunkBytes: number; signal?: AbortSignal }) {
5591
if (this.closed || ref.locator.type !== "http" || !Number.isSafeInteger(options.chunkBytes) || options.chunkBytes < 1) throw new Error("Reader unavailable");
5692
const source = ref.locator.url; const url = new URL(source);

0 commit comments

Comments
 (0)