border-customs 0.6.0 → 0.8.0

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 CHANGED
@@ -491,7 +491,7 @@ border <command> [options]
491
491
  | `llm-ingest <findings.json>` | validate agent findings and record the combined verdict |
492
492
  | `scan <[ecosystem:]name@version>` | inspect a third-party published artifact for residue before installing it |
493
493
  | `exfil <rev\|url>` | read-only exfil lens over a local rev or a remote URL (tip tree by default) |
494
- | `opencode <install\|status\|uninstall>` | install/status/uninstall the official OpenCode plugin adapter |
494
+ | `opencode <install\|status\|uninstall\|inspect>` | install/status/uninstall the official OpenCode plugin adapter; `inspect` audits this machine's V1 load surface |
495
495
 
496
496
  Every subcommand accepts the same global flags (verified against `border <cmd> --help`):
497
497
 
@@ -717,7 +717,7 @@ plugin entry ships as `exports["./server"]`):
717
717
  ```jsonc
718
718
  {
719
719
  // pin the exact version — an @latest channel hits the registry on every cold start
720
- "plugin": ["border-customs@0.6.0"]
720
+ "plugin": ["border-customs@0.8.0"]
721
721
  }
722
722
  ```
723
723
 
@@ -738,9 +738,21 @@ exit 2 and never overwritten, uninstall removes only marker-bearing files, and i
738
738
  report `up to date`. Either route, restart opencode afterwards — tools and commands are
739
739
  scanned at startup.
740
740
 
741
+ **OpenCode 2 (beta) compatibility (0.7.0).** The same module is dual-shaped: V1 hosts call
742
+ `server()` (object entry form, opencode ≥ 1.18.29 required); V2 hosts (`@opencode-ai/cli@beta`,
743
+ bin `opencode2`) call `setup(ctx)` and register the identical tool through
744
+ `ctx.tool.transform` — hand-written `{id, setup}` (the SDK's `define()` is identity), plain
745
+ JSON Schema input, zero V2-SDK runtime dependency. Live-verified on beta-19271 via
746
+ project-local `.opencode/plugins/` (both single-file and package-directory shapes; directory
747
+ discovery resolves the root `index.ts` re-export, which is why the package ships it). V2
748
+ activates plugins lazily — listing them requires `POST /api/plugin/await-activation` first.
749
+ The `/border` slash command stays V1-only until the V2 command-delivery API settles; the
750
+ `border` tool is the supported V2 surface. Config keys are shared (V2 normalizes
751
+ `plugin`→`plugins`). Global file-drop discovery under V2 is untested and not claimed.
752
+
741
753
  What loads is one agent tool `border` plus a `/border` slash command. The tool spawns the CLI
742
754
  argv-only (no shell; 300 s cap, 64 KiB per stream) restricted to the closed command list
743
- `check`, `push`, `status`, `llm-request`, `llm-ingest`, `scan`, `roundtrip`, `--help`, and
755
+ `check`, `push`, `status`, `llm-request`, `llm-ingest`, `scan`, `roundtrip`, `exfil`, `--help`, and
744
756
  renders the exit-code contract unchanged: 0 pass, 1 gate-blocked or partial push, 2 the gate
745
757
  could not answer — which since 0.5.1 also covers "no border CLI found or verified": the tool
746
758
  answers exit 2 cannot-answer and spawns nothing else. The tool REFUSES `--yes` — a real push
@@ -757,6 +769,18 @@ HTTP assertions — the `border` tool id present, `/border` present exactly once
757
769
  fails); plugin load failures are silent, so judge by the HTTP responses, never by the exit
758
770
  code.
759
771
 
772
+ `border opencode inspect [--json]` audits this machine's **V1 load surface** as one gate-owned
773
+ command: six read-only aspects — cache self-consistency (package.json vs plugin marker line),
774
+ spec freshness (exact pin offline; dist-tag against the registry), the 0.5.1 identity handshake
775
+ replayed against the CLI resolver's candidate order, Route A byte parity, Route A∧B dual-registration
776
+ collision, and the scope declaration — aggregated fail-closed: any FAIL exits 1, else any CANNOT
777
+ exits 2, all PASS exits 0. The certificate boundary is printed, not implied: `--json` carries
778
+ `scope:"v1"`, and V2 load surfaces (project-local `.opencode/plugins/`, npm-prefix layouts, lazy
779
+ activation) are a DECLARED BLIND SPOT pending V2 stabilization — a PASS is never evidence that
780
+ "the whole machine is clean". The tool never writes: `--purge` lives in the drift-watch cron shell,
781
+ which is now a thin consumer of `inspect --json` with the rc contract unchanged
782
+ (FRESH 0 / DRIFT 1 / NOT-CACHED 0 / CANNOT 2).
783
+
760
784
  ## What this is not
761
785
 
762
786
  - **Not a vault or secret manager.** It detects credentials on their way out; it does not
@@ -803,6 +827,45 @@ code.
803
827
 
804
828
  ## Changelog
805
829
 
830
+ ### 0.8.0 (2026-09-30)
831
+ - Add: **`border opencode inspect [--json]`** — the load-surface self-audit becomes a gate-owned
832
+ surface: six read-only aspects (cache self-consistency, spec freshness vs pin/registry, identity
833
+ handshake replay, Route A byte parity, Route A∧B dual-route collision, scope declaration),
834
+ fail-closed aggregation FAIL 1 > CANNOT 2 > PASS 0. `--json` pins `scope:"v1"` — V2 load surfaces
835
+ are a declared blind spot, not silently covered. Zero writes; `--purge` stays in the cron shell.
836
+ - Change: **`exfil` joins the plugin's closed command list** (owner ruling R1 under the four-question
837
+ eligibility gate — no mutation, no credentials, no foreign-byte execution, no local egress;
838
+ packet: `.omo/evidence/F-EXFIL-ALLOWLIST-RULING-2026-09-30.md`). `border opencode …` verbs stay
839
+ terminal-only: they mutate `$XDG_CONFIG_HOME` and fail the gate's first question.
840
+ - Change: `tools/plugin-drift-watch.mjs` retires to a thin shell over `border opencode inspect --json`
841
+ (single source of verdicts); the cron rc contract is byte-stable — FRESH 0, DRIFT 1, NOT-CACHED 0
842
+ (special-cased per ruling B2), CANNOT 2 — and the crontab line is unchanged.
843
+
844
+ ### 0.7.1 (2026-09-30)
845
+ - Fix: **release-gate test ruler race** (`test/push.integration.test.ts` I4) — the
846
+ "dry-run made ZERO verdaccio requests" invariant counted lines in verdaccio's stdout
847
+ log, an eventually-consistent view: on a saturated CI runner the gate-leg probe lines
848
+ of an earlier phase land after the snapshot, inflating the dry-run delta (v0.7.0 CI
849
+ run 36671401844 failed `6 !== 2` while the identical bytes pass locally). The suite
850
+ now takes every snapshot through a FIFO drain barrier (sentinel request whose log line
851
+ must be visible before counting; sentinel traffic is excluded from the count). The
852
+ zero-network dry-run contract itself is unchanged — verified request-free on both sides
853
+ of the barrier; only the measuring instrument was repaired.
854
+
855
+ ### 0.7.0 (2026-09-28)
856
+ - Add: **OpenCode 2 (beta) plugin compatibility** — the plugin module is dual-shaped
857
+ (`{id, setup}` for V2 hosts, frozen V1 `server()`), sharing the pre-spawn
858
+ allowlist/`--yes` gates and the 0.5.1 identity handshake across both shapes;
859
+ refusal/handshake/rendering byte-parity between edges is pinned by
860
+ `test/opencode.v2shape.test.ts` while the V1 path stays 16/16 on unmodified
861
+ tests. Root `index.ts` re-export shipped for V2 directory discovery (which
862
+ bypasses the exports map; live-proven on `@opencode-ai/cli` beta-19271).
863
+ - Fix: the live V1 probe is anti-vacuous — opting in (`BORDER_OPENCODE_PROBE=1`)
864
+ without a resolvable binary or built dist now FAILS naming the remedy instead
865
+ of skipping green-adjacent (`BORDER_OPENCODE_BIN` override added).
866
+ - Docs: support matrix (V1 object entry ≥ 1.18.29; V2 project-local verified;
867
+ `/border` command V1-only pending the V2 delivery API).
868
+
806
869
  ### 0.6.0 (2026-09-25)
807
870
  - Add: **the exfil lens** — five rules (`exfil-rfc1918`, `exfil-ssh-target`,
808
871
  `exfil-home-path`, `exfil-cred-location`, `exfil-host-profile`) that flag
@@ -963,6 +1026,12 @@ MIT, see [LICENSE](LICENSE).
963
1026
 
964
1027
  ## 中文概要
965
1028
 
1029
+ **0.8.0(2026-09-30)装载面自审计**:`border opencode inspect [--json]` 把散在插件、cron 脚本与安装器里的装载检查制度化为门禁自有表面——六个只读方面(缓存自洽:package.json 对插件 marker 行;spec 新鲜度:exact pin 离线比、dist-tag 问注册表;身份握手按 CLI 候选序重放;Route A 字节 parity;A∧B 双注册相撞;scope 声明),fail-closed 聚合:任一 FAIL 退 1,否则任一 CANNOT 退 2,全 PASS 退 0。`--json` 面值钉死 `scope:"v1"`:V2 装载面(项目本地 `.opencode/plugins/`、npm-prefix 布局、懒激活态)是**声明的盲区**而非被覆盖——PASS 永远不等于"全机干净"。工具零写入;`--purge` 留在 cron 薄壳,壳的 rc 合同原样保持(FRESH 0 / DRIFT 1 / NOT-CACHED 0(裁定 B2 特判)/ CANNOT 2),crontab 一行未动。exfil 依主人裁定(R1)进插件命令闭集——四问判据全过:不变异、不碰凭据、不执行外来字节、不外传本地数据;`border opencode` 动词组因写 `$XDG_CONFIG_HOME` 留在终端侧。裁决包:`.omo/evidence/F-EXFIL-ALLOWLIST-RULING-2026-09-30.md`。
1030
+
1031
+ **0.7.1(2026-09-30)发布门禁尺伤修复**:I4 集成测试的"dry-run 对 registry 零请求"断言按 verdaccio stdout 日志行计数,而日志是最终一致视图——CI 满载 runner 上更早阶段的合法 probe 行晚于快照落盘,把差额错记到 dry-run 头上(v0.7.0 run 36671401844 红 `6 !== 2`,同字节本机绿)。修复=尺子不松语义:所有快照前后加 FIFO drain 屏障(哨兵请求行可见⇒先前请求行必已落盘;哨兵流量不计入计数)。dry-run 零网络契约本身未动,两侧实测请求为零。
1032
+
1033
+ **0.7.0(2026-09-28)OpenCode 2(beta)兼容**:插件模块双形状——V1 宿主调 `server()`(对象入口需 ≥1.18.29),V2 宿主(`opencode2`)调 `setup(ctx)` 经 `ctx.tool.transform` 注册同一个工具;spawn 前门(allowlist/`--yes`)与 0.5.1 身份握手两形状共享,双边渲染逐字 parity 由测试钉死。V2 懒激活(断言前须 `POST /api/plugin/await-activation`);`/border` 命令暂仅 V1(V2 投递 API 未定形);已在 beta-19271 项目本地两形态活体验证。V1 活体探针反真空化:opt-in 缺前提即 FAIL 点名补救。
1034
+
966
1035
  border 是一个 fail-closed(失败即拦截)的推送前门禁 CLI:`npm install -g border-customs` 安装,在仓库里跑 `border check`。它扫描 git 历史、工作区(未跟踪文件同样是一等输入)、归档、tag 注释和将要发布的 npm/PyPI/crates/RubyGems 字节,检出密钥与供应链风险;只有当"当前状态指纹"存在新鲜且完整的 PASS 记录时才允许 `border push --yes` 放行。指纹是 sha256(head、porcelain 摘要、规则哈希、暴露面、ref 集合、有效目标)六元组,任何一处变动,旧的 PASS 立即失效,必须重查。流水线:gitleaks(历史+工作区+tag,内置 8.30.1 规则,仓库自带的 ignore 文件直接判 CRITICAL)+ secretlint(进程内,AWS Key 规则强制开启)+ 原生规则(AI 会话产物闭集、提交身份白名单含传输对象检查)+ 注册表预检(版本已存在=必须 bump,名称被外人占有=拒绝;空响应/超时/解析失败一律 exit 2,沉默绝不等于不存在)。构件只构建一次进 `.border/dist/`,扫描的就是发布的字节,发布时再哈希比对,不一致直接拒发。跳过台账让重复检查不到 1 秒,但回放前先重算指纹并重新 pack 验证新鲜度。报告只输出掩码片段(sha256 摘要 + 前4…后4)。push 是多目标状态机,多 remote 先做全有或全无的 fast-forward 预检,永不 force-push;npm/twine/cargo/gem 的凭据经 stdio 透传,border 从不触碰。0.2.0 新增 crates.io 与 RubyGems 通道(公开 crates.io 固定、rubygems 可用 host 覆盖私有镜像),既有配置行为不变。0.3.0 新增残留扫描(residue gate):发布字节里的安装期钩子按 T0-T4 闭集签名表分类(闭集 T1 树内钩子降为 MEDIUM,未知形态原样保留 CRITICAL),外加跨包管理器写入、配对标记缺失(`# BEGIN` 块只检测不豁免)与 gem 不可解析扩展共七条 `residue-*` 规则;`residue.enabled` 是唯一豁免开关且改动指纹使旧 PASS 失效,规则表与分类器源码进入 rulesHash,改一个签名即强制重查。静态分析只证明"能力"不证明"事实",border 不是恶意软件沙箱,从不执行被读代码。0.4.0 新增证明阀
967
1036
  `residue.requireProof`(默认关,strict):开启后,发布构件若触发阻断级 `residue-*` 发现,通道 PASS
968
1037
  还要求账本里存在按其 sha256 索引且 rulesHash 新鲜的 `border roundtrip` 实测记录——check 自身从不
@@ -994,6 +1063,6 @@ border 是一个 fail-closed(失败即拦截)的推送前门禁 CLI:`npm install
994
1063
 
995
1064
  发布一致性(0.4.1):起因是 aihr 事故——PyPI 上标为 0.2.2 的 wheel 里 `__init__.__version__` 却写着 0.2.1,构件名、元数据与实际模块行为三者不一致,所有 `==0.2.2` 的用户静默装上了旧行为;同类漂移在本仓库也出现过两次(npm tarball 的 User-Agent 停在 0.3.0 而 package.json 是 0.3.1;package-lock.json 根版本两个 release 一直躺在 0.1.0)。发布阶段扫描器因此在构件内部逐源交叉核对版本号:`release-coherence-version-drift` 为 CRITICAL(package.json 与被强制打包的 package-lock、wheel 文件名对 dist-info 目录对 METADATA `Version:` 对 `__init__` 字面量、sdist 的 pyproject 对 PKG-INFO/setup 系字面量、Cargo.toml 对已打包的 Cargo.lock、.gemspec 对 metadata.gz、dist 文件名对 sdist 内 PKG-INFO);版本源存在但无法静态解析(动态 `__version__`、`attr:`、`dynamic = ["version"]`、坏 lockfile)判 MEDIUM `release-coherence-unverifiable-source`——不可核查绝不静默算干净;第二版本源根本没被打包时不发现在内,缺源不是漂移,这条边界是承重的,误报的门禁会把用户训练成 `--force`。唯一可静态强制的跨包管理器声明是 opt-in `release.twin`(`{pypi, npm}` 严格 zod 对列表,未知键 exit 2):同一 run 的 dist 里孪生版本不等 ⇒ CRITICAL `release-coherence-twin-drift`,消息点名两个构件。规则源文件与 residue 指纹表一样并入 rulesHash,改一个匹配器即令全部缓存 PASS 失效。
996
1065
 
997
- OpenCode 插件适配层(0.5.0):零手工装载两条路线——主路是在 `opencode.jsonc` 声明 `"plugin": ["border-customs@0.5.0"]`(建议钉具体版本,@latest 每次冷启动都打注册表),opencode 自行下载构件,插件入口走 `exports["./server"]`;插件自带 CLI 解析,顺序为 `BORDER_BIN` 覆盖(独占,坏了就报错、绝不静默回退)> 包内同侧 `dist/index.js`(shebang 直接执行,不可执行则回退 PATH 上的 `node` 承载)> PATH 上的 `border`,免全局安装、免 PATH 配置;0.5.1 起任何候选都必须先过**身份握手**(以 `--help` 应答出 border 横幅与 `usage: border` 才算数),通过之前绝不投递用户 argv,握手不过即 cannot-answer exit 2、不运行任何其他二进制——0.5.0 曾用 `process.execPath` 承载包内 dist,而在插件宿主里 execPath 是 opencode 本体,边检门被静默冒充为宿主(2026-09-22 事故,见 0.5.1 变更日志)。备路是 `border opencode install|status|uninstall` 文件投递,落盘 `$XDG_CONFIG_HOME/opencode` 下的 `plugins/border.ts` 与 `commands/border.md`(无 XDG 回退 HOME/.config):受管文件凭 marker 行识别身份,同名外人文件 exit 2 拒绝、绝不覆盖,卸载只删自己带 marker 的文件,字节相同即 `up to date`。装载面只有一个 `border` 工具(argv-only 生成、无 shell、300 秒与每流 64 KiB 上限,命令闭集 check/push/status/llm-request/llm-ingest/scan/roundtrip/--help)与一条 `/border` 斜杠命令(config 钩子以 `??=` 自注册,用户同名文件命令保持权威)。边界如实说:allowlist 是 UX/防呆而非安全边界——插件进程与 opencode 进程同权,门禁的牙齿在 CLI 与账本,不在适配层;`push --yes` 按设计留在终端人审,工具端机械拒绝,会话内裸 push 本就是 CLI 合同的 DRY-RUN。装载验证走 opt-in 探针 `BORDER_OPENCODE_PROBE=1`(临时 HOME/XDG 起 `opencode serve` 对照组,断言 `border` 工具 id 与恰好一条 border 命令,重复即 FAIL;插件加载失败是静默的,只认 HTTP 响应不认退出码)。两条路线装载后都需重启 opencode:工具与命令在启动时扫描。
1066
+ OpenCode 插件适配层(0.5.0):零手工装载两条路线——主路是在 `opencode.jsonc` 声明 `"plugin": ["border-customs@0.5.0"]`(建议钉具体版本,@latest 每次冷启动都打注册表),opencode 自行下载构件,插件入口走 `exports["./server"]`;插件自带 CLI 解析,顺序为 `BORDER_BIN` 覆盖(独占,坏了就报错、绝不静默回退)> 包内同侧 `dist/index.js`(shebang 直接执行,不可执行则回退 PATH 上的 `node` 承载)> PATH 上的 `border`,免全局安装、免 PATH 配置;0.5.1 起任何候选都必须先过**身份握手**(以 `--help` 应答出 border 横幅与 `usage: border` 才算数),通过之前绝不投递用户 argv,握手不过即 cannot-answer exit 2、不运行任何其他二进制——0.5.0 曾用 `process.execPath` 承载包内 dist,而在插件宿主里 execPath 是 opencode 本体,边检门被静默冒充为宿主(2026-09-22 事故,见 0.5.1 变更日志)。备路是 `border opencode install|status|uninstall` 文件投递,落盘 `$XDG_CONFIG_HOME/opencode` 下的 `plugins/border.ts` 与 `commands/border.md`(无 XDG 回退 HOME/.config):受管文件凭 marker 行识别身份,同名外人文件 exit 2 拒绝、绝不覆盖,卸载只删自己带 marker 的文件,字节相同即 `up to date`。装载面只有一个 `border` 工具(argv-only 生成、无 shell、300 秒与每流 64 KiB 上限,命令闭集 check/push/status/llm-request/llm-ingest/scan/roundtrip/exfil/--help)与一条 `/border` 斜杠命令(config 钩子以 `??=` 自注册,用户同名文件命令保持权威)。边界如实说:allowlist 是 UX/防呆而非安全边界——插件进程与 opencode 进程同权,门禁的牙齿在 CLI 与账本,不在适配层;`push --yes` 按设计留在终端人审,工具端机械拒绝,会话内裸 push 本就是 CLI 合同的 DRY-RUN。装载验证走 opt-in 探针 `BORDER_OPENCODE_PROBE=1`(临时 HOME/XDG 起 `opencode serve` 对照组,断言 `border` 工具 id 与恰好一条 border 命令,重复即 FAIL;插件加载失败是静默的,只认 HTTP 响应不认退出码)。两条路线装载后都需重启 opencode:工具与命令在启动时扫描。
998
1067
 
999
1068
  外泄透镜(0.6.0):经典密钥扫描器看不见内网 IP、`user@内网主机`、家目录、凭据文件位置、主机画像这类"外泄形状"字面量——它们不是密钥,却如实描述公网仓库身后的私网。0.6.0 加五条规则 `exfil-rfc1918/exfil-ssh-target/exfil-home-path/exfil-cred-location/exfil-host-profile`,每个(规则×分面×通道)格子的**观察严重度**只有单一事实源 `src/exfil/severity.ts`:树面 HIGH 族走双生规则(gitleaks TOML + secretlint pattern,id 逐字节相等、三通道判决对等性有测试钉死),因两引擎都没有 HIGH 档而如实记为 CRITICAL(不硬掰,如实表);树面 MEDIUM 族走原生腿;提交消息面原生独占、id 带 `:message` 后缀,allow 条目按 id 精确匹配、跨面豁免在结构上不可能;tag note 分面归既有引擎腿 `tag-message-secret`,核心绝不重复发。规则源文件并入 rulesHash,改一个匹配器即令全部缓存 PASS 失效。独立只读入口 `border exfil <rev|url>`(默认树尖,`--deep` 补全史 blob 与全部可达提交消息;URL 模式落临时克隆、退出即销毁)。push 之后新增**落地核验**:ls-remote 复读远端尖、必要时精确 fetch 该对象,用同一套机制扫已公开树——干净则追加 `t:"landing"` 账本行并保留原退出码;有阻断发现则 exit 1 并明说 ALREADY PUBLIC;border 只检测、从不抹除;远端不可观测则 exit 2、一条记录都不造(没核验就没事实),已执行的 push 记录原样矗立——账本 append-only,任何情况下不改写历史。测试语料遵守夹具纪律:10.200.0.0/16 为预留夹具段(树面全通道静默、双生规则原生镜像该豁免,消息面例外照红),其余泄漏形状一律由 `assembleOctet`/`assembleHost` 运行时拼装,签出字节里没有完整泄漏 token——border 的透镜对自己的仓库绿,红故事照常武装。真值通道 `BORDER_EXFIL_TRUTH=1` 为 opt-in,红锚点带生命周期条款:上游作者清理后按设计转绿、留档不判败。
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "border-customs",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "border-customs",
9
- "version": "0.6.0",
9
+ "version": "0.8.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@opencode-ai/plugin": "^1.17.4",