Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions 20-product-tdd/cross-unit-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Record durable data and behavior contracts that span more than one unit reposito
- Registry owns hosting and executable API details; authors own site content and structure.
Documentation does not become an installable Distribution or alter Host lifecycle authority.

## Twitter 归档信息与接入契约

[Twitter 归档信息与接入契约](twitter-archive-contract.md)拥有 `inkcre/twitter` 各 Distribution 共同使用的 User/Post/Media 原生信息、Resolver 与关系含义、旧帖子过渡,以及 x-monkey 协议的 JWT 会话和媒体按需读取边界。各 Unit 保留自己的 HTTP 注册、持久化实现、采集适配与界面代码。

## Info-Base Ownership Contract

- Collection, organization, application, graph authority, resolver/storage composition, and
Expand Down
15 changes: 8 additions & 7 deletions 20-product-tdd/peer-database-runtime-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,21 @@ The shared browser-to-database and browser-to-runtime token contract is:
- audience: required and exactly `inkcre-api`;
- issued-at (`iat`): required;
- expiry (`exp`): required;
- maximum lifetime: 24 hours;
- signature secret: runtime-owned and at least 32 bytes.

A signer may move `iat` a small, bounded amount into the past to tolerate ordinary clock skew
between independently deployed Peers and protocol runtimes. It must derive `exp` from that adjusted
`iat`, so the allowance never increases the 24-hour maximum token lifetime. Clock-skew handling
belongs to the signing boundary; database queries and domain callers do not retry or rewrite tokens
individually.
`iat` 与 `exp` 必须是有效数值时间,`exp` 必须晚于 `iat`;接收端校验允许的未来 `iat` 时钟偏差和实际过期时间。协议不设置 `exp - iat` 的最大值,也不将某个部署的签发默认值作为准入上限。

签发者拥有自己的默认有效期与配置方式。服务端签发默认值由环境配置,配置值必须是正整数;默认值只影响以后签发的 JWT,降低默认值不会撤销尚未过期的既有凭据。静态客户端的默认值属于该签发者的策略,不从数据库准入合同派生。

签发者可以将 `iat` 向过去调整一个有界的小幅度,以容忍独立部署的 Peer 与协议运行时之间的普通时钟偏差,并从调整后的 `iat` 计算 `exp`。时钟偏差处理属于签发边界;数据库查询和业务调用者不逐个重试或改写 token。

PostgREST and native HTTP runtimes validate the same claim vectors. A missing, malformed,
expired, overlong, wrong-secret, wrong-role, wrong-issuer, or wrong-audience token is denied.
expired, wrong-secret, wrong-role, wrong-issuer, or wrong-audience token is denied.
The contract does not assign secret custody to `impl-py`; each deployment supplies the same
secret independently to the runtimes that validate it.

Extension 可以为同类 JWT 定义有签名保护的用途声明和续签条件,但不能据此声称缩小原有 Peer 权限。Twitter 归档连接的传输、签发默认值和自动续签见 [Twitter 归档信息与接入契约](twitter-archive-contract.md)。

## Artifact And Environment Contract

- The core database runtime is published as an OCI image addressable by immutable digest.
Expand Down
4 changes: 1 addition & 3 deletions 20-product-tdd/semantic-retrieval-and-peer-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,7 @@ The first admitted exact capability IDs are:

## Runtime Authentication And Advertisement

- Peer HTTP uses the shared `HS256` token contract with role `authenticated`, issuer
`inkcre-peer`, audience `inkcre-api`, required `iat`/`exp`, a 24-hour maximum lifetime, and
deployment-owned secret material of at least 32 bytes.
- Peer HTTP 使用 [Peer Database Runtime Contract](peer-database-runtime-contract.md#jwt-contract) 的共享 `HS256` JWT:role 为 `authenticated`,issuer 为 `inkcre-peer`,audience 为 `inkcre-api`,必需 `iat`/`exp`,部署持有至少 32 字节的签名秘密。签发默认有效期属于签发者配置,接收端不把它作为最大寿命。
- A capability is advertised only after its inbound is registered and routable. Withdrawal
precedes runtime unmount/close. Ordinary Peer-row updates do not imply liveness.
- The deployment owns each Peer identity and runtime config, including the public base URL
Expand Down
98 changes: 98 additions & 0 deletions 20-product-tdd/twitter-archive-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Twitter 归档信息与接入契约

本契约由 `inkcre/twitter` 拥有,约束 Python 与 Web Distribution 共同写入和解释的原生信息、关系及 x-monkey 接入语义。持久信息属于共享 info-base;采集入口或执行 Peer 停止后,信息仍可由其他具备相应能力的 Peer 使用。各 Unit 自己拥有持久化查询、事务实现、HTTP 注册、采集适配和界面实现。

## 原生信息与身份

User、Post、Media 是 Twitter Extension 自有的共同信息模型,供采集适配器、协议适配器和各 Distribution 的 Resolver 共同使用。x-monkey 的兼容基线为 `v0.2.0` / `0fe02c7f291bbdc48a6376bc30d8a98f63e94136`;该版本的 [archive/model.ts](https://github.com/steven-vvv/x-monkey/blob/0fe02c7f291bbdc48a6376bc30d8a98f63e94136/src/archive/model.ts) 只定义这个客户端适配器的固定 DTO,不拥有共同模型的演进或合并规则。三类实体通过 ID 引用彼此;同一种实体的原生 ID 决定合并身份,不能使用 handle、显示名或 JavaScript 数字替代。

| 原生实体 | Resolver | 完整记录的必需字段 | 保留的信息 |
| --- | --- | --- | --- |
| User | `extensions.twitter.user.v1` | `id`、`handle`、`name` | 作者资料、简介富文本、来源链接、账号属性,以及逐项计数和观察时间。 |
| Post | `extensions.twitter.tweet.v2` | `id`、`authorId`、`createdAt` | 正文富文本、长文结构、作者与上下文 ID、媒体顺序、卡片、投票、地点、计数和其他已捕获源事实。 |
| Media | `extensions.twitter.media.v1` | `id`、`type`、`url` | 图片或封面 URL、尺寸、alt、视频 variants、来源 ID、可用性及其他已捕获媒体元数据。 |

每个根 Block 的 content 保存共同模型的结构化 JSON,而不是清洗后的阅读文本或另一份协议归档。已保存字段、Resolver ID、Block ID 和引用含义保持兼容,不因适配器与共同模型分离而迁移数据。阅读投影只使用其中部分字段时,不得删改已接纳的其他源事实。原生 ID 为正十进制字符串;正文实体区间是 Unicode 码点的半开区间 `[start, end)`;原生时间使用 UTC ISO-8601,计数观察时间保留采集响应完成时的精度。长文 Draft.js 区间继续采用源数据的单位,不擅自转换成正文区间。

字段缺省表示未知。合并不得把缺省变成 `null`、空数组、空正文或否定值;纯转帖可以只有必需字段和 `repostOf`。嵌套对象递归合并,已出现的数组整体替换并保留顺序。接收到明确引用的 ID、但没有完整记录时,可以建立 content 严格为 `{"id":"..."}` 的身份根;不能编造作者资料、正文或未捕获的上下文。后续完整记录补齐同一根。

x-monkey 与 Bookmark 是共同信息的两个入口。x-monkey 在边界将固定 DTO 转为共同模型;官方 API 和 Twikit 也把实际观察到的已支持字段投影到共同模型。持久化和 Resolver 不依赖 wire DTO 或协议状态。同一 Post ID 补充同一根,Bookmark 不能用加工后的媒体占位文本覆盖已保存的完整正文;作者 handle 不能充当作者 ID,conversation ID 不能充当回复父帖 ID。当前 provider 适配器没有保存全部原始响应,未映射的未知字段不构成已保留事实。

匹配时优先使用新版根,再寻找同一原生 ID 的 `extensions.twitter.tweet.v1` 根。旧根获得足够新版事实时原地升级并保留 Block ID,使已有组织和引用继续成立;没有重新采集的 v1 记录仍由 v1 decoder 读取。若历史上已有重复,使用固定目标,不承诺本次搬迁或清理全部重复。普通 Twitter 写入入口对身份匹配、合并和受管关系使用同一串行化边界,提交失败不能留下部分批次;这不将所有 info-base 原始写入都纳入 Twitter 的身份保证。

## 稀疏计数与重复提交

User/Post 的每项计数与同名 `countsObservedAt` 成对处理。以下规则属于 Twitter 的共同模型;客户端适配器保持相应兼容行为,但不将上游实现当作合并权威。比较按毫秒时间值处理,持久记录保留输入时间字符串的精度:

- 新旧值都有观察时间时,较旧观察不能覆盖较新值;相同或较新时间可以覆盖。
- 相同计数但较新的观察时间仍需保存;不能用客户端“内容是否改变”的返回值忽略元数据写入。
- 无时间的新计数可以更新;值改变时移除该项旧时间,避免用旧观察时间标记新值。值未改变时保留已有时间。
- 没有对应计数的孤立观察时间不更新记录;未提交的其他计数和时间保持。

完整信息、补充信息与原样重放均保持同一 Block 身份。较旧的客户端捕获可能继续显示计数差异;不能为消除该界面差异回退已保存的新观察。

## 可导航关系

Post 的受管出边 content 使用以下 JSON grammar;`type` 是表中的一个实际值,只有 `media` 带从 0 开始的 `position`。

```json
{"schema":"extensions.twitter.reference.v1","type":"media","position":0}
```

| `type` | 目标 | 对应事实 |
| --- | --- | --- |
| `author` | User 根 | `authorId`。 |
| `media` | Media 根 | 依次连接 `mediaIds`、`article.coverMediaId`、`article.mediaIds`,按首次出现去重后形成的有序序列,`position` 为该序列的 0-based 下标。 |
| `reply` | Post 根 | `replyTo.postId`。 |
| `quote` | Post 根 | `quote.postId`。 |
| `repost` | Post 根 | `repostOf`。 |

这些边由对应原生引用维护;只删除或替换本 grammar 管理的边,不改动用户组织关系、既有引用或旧 v1 `attachment` / `entities:url` 边。只知道 ID 的目标仍可导航到身份根,并明确表示未捕获内容。原生记录里的其他 ID 不因此获得一条未定义的关系类型。

本版本的 Media 根只保存原生元数据,不预建持久的 `core.image.v1` / `core.video.v1` 内容 Block,也不建立 Media 到这些 Block 的 `content` 关系。Post 到 Media 的受管引用保持不变,媒体身份、字段合并和 query 仍以原生 Media 根为准。

## 媒体读取与独立使用

本版本只保存 Media 元数据,不持久化图片或视频字节,也不创建媒体下载 Job。用户明确展开时,Twitter Distribution 选择已捕获的图片 URL,或视频/GIF 可用 MP4 variants 中码率最高的地址,交给现有 HTTP Storage 读取;这次读取不创建持久媒体内容 Block。URL 的 HTTP(S) 校验、超时、体积限制和重定向处理由通用 Storage 合同拥有。HLS 继续保留为元数据,不作为视频本体加载。

保存、query、列表、文本投影与词法索引不得为了这些 Twitter 媒体读取外部字节;用户明确展开后,才使用现有 HTTP Storage 的超时、大小限制和失败反馈。普通媒体内容 Block 可以按其既有 [词法投影与媒体物化合同](feature-retrieval-and-media-interpretation.md#faithful-media-materialization) 被独立处理;为避免全库索引维护提前读取 Twitter 媒体,本版本不预建这类 Block,不改变通用媒体的词法物化政策。

源站 URL 失效时保留文字与元数据,界面明确表示媒体读取失败。`accepted`、`unchanged` 和 `found` 均不承诺媒体长期可用,连接说明必须说明未自动转存媒体,以免沿用客户端的媒体转存文案产生误解。

Post/User Resolver 通过既有投影与索引维护提供原帖 ID/链接、作者和已捕获正文/长文线索;阅读端展示正文、长文、出处和已捕获上下文,并安全解释结构化富文本,不执行归档中的 HTML 或脚本。关闭 x-monkey 后,这些能力仍使用共享信息。未捕获的上下文只显示其已知身份或原生不可用说明,不在阅读时额外抓取完整线程。

媒体本体持久化属于后续能力:先具备适合大媒体的 Storage,再由明确设置开关选择异步 Job;本版本不提供没有实现的开关、保存进度或重试界面。

## x-monkey 协议

用户配置的 Base URL 为执行该 HTTP 能力的 Core 公共基址加 `/twitter/x-monkey`,保留部署路径前缀。

| 相对 Base URL 的路径 | 行为 |
| --- | --- |
| `GET /api/v1/session` | 返回会话状态、期限及 `account_url`;未连接时提供连接指引。 |
| `POST /api/v1/archive/query` | 按各类 ID 批量返回 `found`、`missing` 或 `failed`。适配器将完整共同模型显式投影为固定协议 DTO;严格身份根为 `missing`;损坏记录为 `failed`。 |
| `POST /api/v1/archive/submit` | 接收固定 User/Post/Media DTO 批次,转为共同模型写入后,由适配器产生逐项状态与汇总;原样重放不新增根或受管关系。 |

一次 submit 的共同事实与受管关系原子保存;数据库失败使整个批次回滚并返回请求错误。结果中的 `accepted` 表示已保存或合并,包括只有计数观察时间变化的持久更新;`unchanged` 表示没有持久变化。不能用成功汇总掩盖失败实体,也不能因本版本有意不下载媒体而返回 `partial`。

读取与写入用例只返回信息、无效记录、Block 身份和实际变化,不产生 x-monkey 状态。协议请求的条数限制与固定 DTO 校验留在适配边界,不成为共同模型的集合上限。适配器按固定 DTO 字段集递归导出响应,其中声明为自由 JSON 的文章 data 仍保留其内容;共同模型以后增加的字段不会自动泄漏到旧协议,也不会因旧协议投影而从持久记录删除。

协议不依赖 X OAuth;OAuth 仍只服务 Bookmark 采集。浏览器和脚本管理器适配属于 x-monkey。该接入不提供远端历史浏览、检索或恢复接口,也不补抓请求外的线程和历史。

## JWT 连接与续签

协议复用 [Peer JWT](peer-database-runtime-contract.md#jwt-contract),不新增 PAT、refresh token、签名秘密或会话表。JWT 仍具既有完整 Peer 权限;用途声明与 Cookie 路径都不构成降权或单会话撤销。

用户在 x-monkey 设置远端数据库地址并检测;未连接时通过 `account_url` 打开同一 Core 自托管的 `/twitter/x-monkey/account`。用户确认是自己信任的 HTTPS 部署后,在账户页输入该部署现有的 `JWT_SECRET`。页面通过浏览器 WebCrypto 本地签发短期 Peer JWT,以同 Origin Bearer 请求提交到 `/twitter/x-monkey/session`;Core 复用普通 Peer JWT 认证后建立归档 Cookie。页面再通过 session 请求确认浏览器实际接受 Cookie,才显示 `Connected`、连接的 Peer 与到期时间;用户返回脚本再次检测。连接不要求安装 InKCre Web 或 Twitter Web Distribution。

账户页不持久化或经网络发送 `JWT_SECRET`,不加载第三方脚本,不接受任意回跳地址;本地签名不意味着恶意或被篡改的页面安全,部署页面本身必须可信。可选的 Web Setup 保留 `x_monkey_base_url` 供用户复制,并以普通链接打开该 Core 的账户页,不处理密钥或签发 JWT。

会话 JWT 带有签名保护的 `session_use="twitter_archive"`,其默认有效期为 7 天,由 Core 环境配置。首次连接与续签使用同一签发配置;静态 Web 不读取或复制该期限。普通 Peer JWT 只在显式连接入口证明已有权限,普通业务请求不会自动将它升级为可续签会话。

有效 session/query/submit 请求通过会话 JWT、用途及来源检查后,若剩余有效期不超过当前配置寿命的一半,Core 签发新的 `iat`/`exp` 并通过 `Set-Cookie` 更新 Cookie。未到窗口不重写 Cookie。过期、无效或没有该用途的 token 不能续签,其他 Core 请求不触发续签。脚本不需要 refresh endpoint 或续期定时器;停止请求后,在最后签发 JWT 的 `exp` 到期。

本版本没有从首次授权开始计算的绝对会话上限;活跃会话和已复制的合格 JWT 都可以持续续签。断开仅清理当前浏览器 Cookie,不撤销已复制 JWT;部署更换既有 JWT_SECRET 后按既有机制失效。较长 JWT 同时扩大被复制凭据的可用时间。

正式 HTTP 接入使用 HTTPS,Cookie 为 Secure、HttpOnly、SameSite=None,Path 限于保留部署前缀的 Twitter 协议路径,期限不晚于 JWT `exp`。只有这些路由读取协议 Cookie,Core 其他受保护路由继续要求既有 Bearer。无关网页不能读取协议响应或利用 Cookie 发起归档写入,明显的子资源嵌入请求不能触发续签;不能把宽松 CORS、JSON 格式或固定客户端头视为凭据。GM 后台请求可能没有网页 Origin,接收端不能据此识别所有触发无 Origin session GET 的页面。JWT 不出现在 URL 或日志中,JWT_SECRET 不经网络发送给 Core、X 或脚本。

Python 与 Web Distribution 依靠本契约配套;其 [Native Release 独立版本](extension-release-and-installation.md),不要求版本数字相同。旧阅读端不能假定已支持新版 Resolver,部署需要选择能够解释本契约的 Distribution。
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [System state and authority](../20-product-tdd/system-state-and-authority.md)
- [Cross-unit contracts](../20-product-tdd/cross-unit-contracts.md)
- [跨 Peer 可观测性契约](../20-product-tdd/observability-contract.md)
- [Twitter 归档信息与接入契约](../20-product-tdd/twitter-archive-contract.md)
- [Knowledge capability contract](../20-product-tdd/knowledge-capability-contract.md)
- [Semantic retrieval and Peer capabilities](../20-product-tdd/semantic-retrieval-and-peer-capabilities.md)
- [Feature retrieval and media interpretation](../20-product-tdd/feature-retrieval-and-media-interpretation.md)
Expand Down
15 changes: 8 additions & 7 deletions website/content/en/guide/sources/twitter.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,19 @@ requirements. An app-only bearer token is not a substitute for authorizing your
The browser-side Twitter Extension provides a visual **Setup** wizard. You do not need the OAuth
script when that wizard is available with a compatible Core Extension.

Use `inkcre/twitter` version `0.4.1` with Core Host SDK `0.3.x` and a compatible Web Host. Both
Hosts use the same installed version: Core runs the Python collector, while the browser loads the
setup wizard from that release's Module Federation distribution. If browser enablement reports a
missing distribution, check the release publication and Registry configuration. Do not downgrade to
`0.3.0`: its Python package targets Core Host `0.1.x`.
The Bookmark example uses Python `inkcre/twitter` version `0.4.1` with Core Host SDK
`>=0.3.1 <0.4.0`. Select a compatible Web Module Federation distribution separately: Core runs the
Python collector, while the browser runs the setup wizard and content views. These are independently
versioned releases; equal version numbers do not establish compatibility. If browser enablement
reports a missing distribution, check its release publication, Host compatibility and Registry
configuration. Do not downgrade to `0.3.0`: its Python package targets Core Host `0.1.x`.

### 1. Open Setup

1. [Prepare the Extension](/guide/extensions) on Core with name `inkcre/twitter` and version
`0.4.1`, then enable it on Core.
2. In **Extensions**, choose **Enable…** on `inkcre/twitter`, select **This browser**, confirm, and
click **Setup**.
2. Install a compatible Web Module Federation distribution for **This browser**. In **Extensions**,
choose **Enable…** on `inkcre/twitter`, select **This browser**, confirm, and click **Setup**.
3. Choose your online Core Peer in the wizard. Enable Twitter there if prompted. Core handles the
callback and collection; browser enablement alone is insufficient.

Expand Down
Loading