From 256bcc0e66ebe8a946e300b8d465731caea02bf4 Mon Sep 17 00:00:00 2001 From: wefio <48851810+wefio@users.noreply.github.com> Date: Thu, 1 Oct 2026 20:45:43 +0800 Subject: [PATCH] feat(verification): classify and gate policy words in the mechanism surface Implement the explicitly approved mechanism-not-policy classification/check slice. Keep a maintained vocabulary and exact counts keyed by path, named scope and word; classify all 307 occurrences in 123 records across 28 files. Scan identifiers and runtime/SQL literals, ignore TypeScript comments, and refuse new, changed or stale sites without a reviewed table update. Wire check:policy-words into the shared blocking static contract and its agent plan, with a guard against duplicate/missing execution. Move the paired record to implemented with explicit approval, update inbound links, and retain the work-shape rewrite, field experiments and frame/storage migration as deferred/unapproved scope rather than claiming separation. Validation: targeted tests 16/16; keyless product tests 1594/1594; scoped agent:verify passed all 16 blocking checks under the existing 150s cap. The initial full run hit that cap and remains recorded as incomplete. Full mutation sweep caught 110/110 and restored 22/22 byte-identically; one repeated-dispatch mutant was caught by its 30s cap, not an assertion. --- agent-context.yaml | 1 + ...2026-09-20-the-program-answers-legality.md | 2 +- ...9-20-the-program-answers-legality.zh-CN.md | 2 +- .../2026-09-21-mechanism-not-policy.md | 113 +- .../2026-09-21-mechanism-not-policy.zh-CN.md | 95 ++ ...-09-24-mutants-are-derived-not-anchored.md | 4 +- ...-mutants-are-derived-not-anchored.zh-CN.md | 4 +- .../2026-09-21-mechanism-not-policy.zh-CN.md | 91 -- .../2026-09-21-the-frame-and-its-storage.md | 2 +- ...6-09-21-the-frame-and-its-storage.zh-CN.md | 2 +- docs/design/ci-cd-and-quality.md | 2 + docs/design/mechanism-in-the-middle.md | 9 +- docs/design/mechanism-in-the-middle.zh-CN.md | 6 +- package.json | 3 +- tests/tools/policy-word-check.test.ts | 110 ++ tests/tools/test-groups.test.ts | 11 + tools/policy-word-check.ts | 179 +++ tools/policy-word-list.ts | 1095 +++++++++++++++++ 18 files changed, 1580 insertions(+), 151 deletions(-) rename docs/decisions/{proposed => implemented}/2026-09-21-mechanism-not-policy.md (52%) create mode 100644 docs/decisions/implemented/2026-09-21-mechanism-not-policy.zh-CN.md delete mode 100644 docs/decisions/proposed/2026-09-21-mechanism-not-policy.zh-CN.md create mode 100644 tests/tools/policy-word-check.test.ts create mode 100644 tools/policy-word-check.ts create mode 100644 tools/policy-word-list.ts diff --git a/agent-context.yaml b/agent-context.yaml index b730e723..5637c2f3 100644 --- a/agent-context.yaml +++ b/agent-context.yaml @@ -153,6 +153,7 @@ routes: - complexity:gate - verify:packages - glossary:check + - check:policy-words - rtm:check - test:product advisory: [verify:research, verify:chaos] diff --git a/docs/decisions/implemented/2026-09-20-the-program-answers-legality.md b/docs/decisions/implemented/2026-09-20-the-program-answers-legality.md index 5fb72b92..65df4bca 100644 --- a/docs/decisions/implemented/2026-09-20-the-program-answers-legality.md +++ b/docs/decisions/implemented/2026-09-20-the-program-answers-legality.md @@ -4,7 +4,7 @@ **Status:** implemented **Approved:** explicit -**Relates to:** [Mechanism, not policy](../proposed/2026-09-21-mechanism-not-policy.md), [Board governance and capability addressing](2026-09-06-board-governance-addressing.md), [Name the collaboration protocol and its task-unit sub-protocol](2026-09-20-name-the-collaboration-protocol.md), [Task unit semantics](../../design/task-unit-semantics.md), [The contract's obligations](../../design/task-unit-semantics-obligations.md), [Protocol-governed collaboration: the parts, the gaps](../../design/protocol-governed-collaboration.md) +**Relates to:** [Mechanism, not policy](2026-09-21-mechanism-not-policy.md), [Board governance and capability addressing](2026-09-06-board-governance-addressing.md), [Name the collaboration protocol and its task-unit sub-protocol](2026-09-20-name-the-collaboration-protocol.md), [Task unit semantics](../../design/task-unit-semantics.md), [The contract's obligations](../../design/task-unit-semantics-obligations.md), [Protocol-governed collaboration: the parts, the gaps](../../design/protocol-governed-collaboration.md) ## Problem diff --git a/docs/decisions/implemented/2026-09-20-the-program-answers-legality.zh-CN.md b/docs/decisions/implemented/2026-09-20-the-program-answers-legality.zh-CN.md index f8812e0b..6475d5a9 100644 --- a/docs/decisions/implemented/2026-09-20-the-program-answers-legality.zh-CN.md +++ b/docs/decisions/implemented/2026-09-20-the-program-answers-legality.zh-CN.md @@ -4,7 +4,7 @@ **Status:** implemented **Approved:** explicit -**Relates to:** [机制,不是策略](../proposed/2026-09-21-mechanism-not-policy.zh-CN.md)、[黑板治理与能力寻址](2026-09-06-board-governance-addressing.zh-CN.md)、[给协作协议及其任务单元子协议命名](2026-09-20-name-the-collaboration-protocol.zh-CN.md)、[任务单元语义](../../design/task-unit-semantics.md)、[契约的义务](../../design/task-unit-semantics-obligations.md)、[协议化协作:组成部分与空缺](../../design/protocol-governed-collaboration.zh-CN.md) +**Relates to:** [机制,不是策略](2026-09-21-mechanism-not-policy.zh-CN.md)、[黑板治理与能力寻址](2026-09-06-board-governance-addressing.zh-CN.md)、[给协作协议及其任务单元子协议命名](2026-09-20-name-the-collaboration-protocol.zh-CN.md)、[任务单元语义](../../design/task-unit-semantics.md)、[契约的义务](../../design/task-unit-semantics-obligations.md)、[协议化协作:组成部分与空缺](../../design/protocol-governed-collaboration.zh-CN.md) ## 问题 diff --git a/docs/decisions/proposed/2026-09-21-mechanism-not-policy.md b/docs/decisions/implemented/2026-09-21-mechanism-not-policy.md similarity index 52% rename from docs/decisions/proposed/2026-09-21-mechanism-not-policy.md rename to docs/decisions/implemented/2026-09-21-mechanism-not-policy.md index dd9b0dc5..7cbedd26 100644 --- a/docs/decisions/proposed/2026-09-21-mechanism-not-policy.md +++ b/docs/decisions/implemented/2026-09-21-mechanism-not-policy.md @@ -2,12 +2,13 @@ [中文](2026-09-21-mechanism-not-policy.zh-CN.md) -**Status:** proposed -**Relates to:** [The program answers legality](../implemented/2026-09-20-the-program-answers-legality.md), [The frame, its data format, and its storage](2026-09-21-the-frame-and-its-storage.md), [Board governance and capability addressing](../implemented/2026-09-06-board-governance-addressing.md), [Protocol-governed collaboration: the parts, the gaps](../../design/protocol-governed-collaboration.md), [Task unit semantics](../../design/task-unit-semantics.md) +**Status:** implemented +**Approved:** explicit +**Relates to:** [The program answers legality](2026-09-20-the-program-answers-legality.md), [The frame, its data format, and its storage](../proposed/2026-09-21-the-frame-and-its-storage.md), [Board governance and capability addressing](../implemented/2026-09-06-board-governance-addressing.md), [Protocol-governed collaboration: the parts, the gaps](../../design/protocol-governed-collaboration.md), [Task unit semantics](../../design/task-unit-semantics.md) ## Problem -Two proposed records each need the same sentence, and neither states it. The legality record says what the +The legality and frame/storage records need the same ownership rule. The legality record says what the program does and what it refuses to decide. The frame and storage record says what the core knows and what it never parses. Those are the same division, seen from the side of the decision and from the side of the data, and because nothing names it, the division has to be re-argued every time a field or a rule appears. @@ -21,7 +22,7 @@ The split also needs to be sharp enough to settle an argument rather than to ser out of the core" does not decide whether `effect` or `operation` may be a column, or whether a legality rule may live in the program at all. -## Proposal +## Decision Take Hydra's principle - a kernel provides mechanisms and refuses policy - and state it as two contracts. @@ -44,11 +45,29 @@ Take Hydra's principle - a kernel provides mechanisms and refuses policy - and s **The decision procedure.** For each field, type and code path, ask whether it is mechanism or policy. A rule's _check_ is mechanism; the rule's _name, wording and enablement_ are policy. -**The mechanically checkable rule.** No policy word may appear in the mechanism layer's code, type -declarations or schema paths. The list is maintained - `patch`, `editable`, `files`, `instruction`, -`checks`, `repair-first` - and a hit is a leak rather than a style question. The list may grow, and adding -a word is recorded. A word alone is not enough to judge: `files` is a policy word in a ticket type and a -mechanism word at a filesystem boundary, so the check carries the path. +**The mechanically checkable rule.** Every vocabulary hit on the declared mechanism surface is +classified as `mechanism`, `policy`, or `undecided`, with a rationale and an exact occurrence count. +[The maintained list and classification table](../../../tools/policy-word-list.ts) own the vocabulary, +scan paths, and classifications. A word alone does not decide its role: `files` can name a ticket payload +or a filesystem boundary. Roles are not files. + +[`check:policy-words`](../../../tools/policy-word-check.ts) is blocking in the shared static contract. +It scans TypeScript identifiers and string/template literals, including SQL, but ignores TypeScript +comments. Camel, snake and kebab spellings count; `dispatch` does not count as `patch`. Each record is +keyed by repository path, enclosing named declaration/method chain (or ``), and word. Line +numbers are diagnostic only; a multiline literal is located at its starting line. Whitespace and +comment changes do not change a record's identity. `npm run check:policy-words -- --list` prints the +classified sites, observed locations and reasons. + +Existing policy hits are explicitly grandfathered, not declared clean. A new site, a count change in +either direction, a stale or duplicate record, invalid classification, invalid TypeScript or missing +scan path fails the check. Removing a leak requires retiring or reducing its record. New occurrences +require an explicit classification and rationale in the same reviewed change; the checker never +refreshes counts automatically. Vocabulary additions are recorded here with their rationale. + +The scope is bounded by the maintained path list, not all source, tests or drivers. Source imports and +schema literals on those paths count too. This implements the approved classification and static-check +slice (original Plan 2–3), not the work-shape conversion. The frame/storage proposal remains proposed. **Rows deliberately left undecided**, so that they are not settled by accident: @@ -58,7 +77,12 @@ mechanism word at a filesystem boundary, so the check carries the path. but whether an input carries content or only a digest is a separate question. - `operation`, which may be policy. -## What this makes of the work shape +## Deferred + +### Work-shape boundary + +The work-shape conversion (original Plan 5) is not part of the approved slice. The patch assumptions +below remain; their classification does not assert that an opaque adapter seam has been implemented. A work shape is not a core concept. It is the policy layer's name for the declaration-and-artifact pair that the core carries without understanding it. The six sites where the patch assumption lives classify as @@ -69,8 +93,8 @@ follows. | `BoardTicket.patch?: FrozenPatchTask & { digest }` | policy | the ticket carries an opaque `declaration` with a digest | | `patchFrozen()` calling `preparePatchWork`, and the `not a patch task` throw | policy inside the core | freezing and field validation move into the shape adapter; the core only hands back the opaque declaration | | `refuseWidening` reading `parent.patch.editable` | check is mechanism, wording and enablement are policy | permission closure becomes a declared constraint the program enforces and reports reasons for | -| the ten `FrozenPatchWork` signatures in the session mechanism | mixed | rendering a declaration into a prompt and submitting an artifact are policy; session lifecycle, metrics and cancellation are mechanism | -| `task_run_tasks.patch_files` / `patch_editable`, parsed in the store | policy in the schema | belongs in the payload document; the mechanism part is the run, task, revision, input, dependency and operation columns | +| `FrozenPatchWork` signatures in the session mechanism | mixed | rendering a declaration into a prompt and submitting an artifact are policy; session lifecycle, metrics and cancellation are mechanism | +| `task_run_tasks.patch_files` / `patch_editable`, parsed in the store | policy in the schema | belongs in the payload document; run, task and revision are mechanism, while input/dependency granularity and operation remain undecided | | drivers asserting `ticket.patch!.digest` | policy assertion | the mechanism assertion is that a declaration carries a digest, that a claim binds the attempt, and that a delivery binds the digest | The legality rule found today is the clearest case of the refinement this record adds: its check belongs to @@ -80,34 +104,35 @@ shape of fix as step 3 of the legality record, which landed on 2026-09-23: repai constraint rather than shared planning policy. Two independent fixes taking the same shape is evidence that the classification is the right one. +### Field experiments and separation evidence + +- `task_run_tasks.effect`, input/dependency granularity and `operation` remain undecided. Their owning + field must be exercised under an alternative declaration before deciding ownership; this gate does + not include those words or resolve them. +- The `files` groups in schema migration and `TaskUnit` are marked `undecided`: a group combines + patch-specific paths with another role. Resolve them by separating the DDL subjects or exercising + an alternative input declaration, not by relabelling the entire group mechanism. +- The remaining permission-closure name/enablement coupling needs a declared constraint while its + check continues to execute in the program and return reasons. +- Zero policy hits and a second work shape requiring no mechanism changes are unverified separation + goals, not consequences of passing this ratchet. The draft's [second-shape experiment](../../design/mechanism-in-the-middle.md#results) + remains evidence of coupling, not a completed adapter migration. + ## Alternatives considered - **Keep the slogan and decide case by case.** Rejected: that is what produced six sites, and it offers no test to apply. -- **Put the principle in the frame record.** Rejected: the principle is wider than the frame - it also - governs the program's share of decisions - so the frame record would become the owner of a rule about the - program. -- **Put it in the legality record.** Rejected for the same reason in reverse: that record is narrower, being - about the program's decisions, and what the core may know is not a legality question. -- **A plugin framework with a registry above the core.** Rejected: no second policy exists to justify the - machinery; one default adapter plus one other shape tests the seam. -- **Decide the undecided rows now.** Rejected: choosing them by preference is precisely what this record - replaces with evidence. - -## Acceptance criteria - -- The classification covers every site that mentions a policy word, each row marked mechanism, policy, or - undecided. -- No policy word appears in the mechanism layer's read and write paths, type declarations, or schema paths, - from a maintained list and checked mechanically. -- A legality rule's name and enablement come from a declaration, while its check runs in the program and - returns a reason per unit. -- Adding a second work shape changes no mechanism code, demonstrated by the value work the data-check - runner already performs. -- Every undecided row is marked as undecided and is resolved by an experiment rather than by preference. -- The legality record and the frame record both point here, so the division has one home. - -## Risks +- **Put the principle in either narrower record.** Rejected: it governs both what the core knows and the + program's share of decisions; neither the frame nor legality record owns the full rule. +- **A plugin framework above the core.** Rejected: one default adapter plus one other shape can test the + seam without building a registry framework. +- **Decide undecided rows by preference.** Rejected: the field experiments remain the deciding evidence. +- **Fail every existing policy hit immediately.** Rejected: the approved slice inventories the debt; + it does not authorize a big-bang work-shape rewrite. +- **A file-level word exemption or a maximum count.** Rejected: it silently admits replacement sites or + leaves dead exemptions. Named-scope exact counts expose moves and reductions for review. + +## Consequences - **Gutting the core.** Hydra's lesson cuts both ways: a kernel with no default policy is unusable. The practical form is that the core may carry one default policy, patch work, and must not require it. @@ -117,13 +142,9 @@ is the right one. policy nobody declared. - **A policy word may legitimately remain for a release.** This record does not require a big-bang rename; it requires that each remaining occurrence is listed. -- **A grep check can be gamed by synonyms.** The check is a floor, not a proof of separation. - -## Plan - -1. This record. No code. -2. The classification table, with the undecided rows marked. -3. The check: a maintained policy-word list plus a script, wired into the static set. -4. Both records point here. -5. Then the work-shape slice: the mechanism side carries an opaque declaration, with patch as the default - adapter rather than a type. +- **The ratchet can be gamed.** Synonyms and same-count replacements within one named scope can evade it. + Classifications and scope changes need review. The check is a floor, not proof of separation. +- **Exact counts cost maintenance.** Intentional code moves and removals require updating the table; + this prevents silent growth but does not prohibit a reviewed policy change. +- The legality and frame/storage records link here for this shared ownership rule. Neither link approves + the separate frame/storage migration. diff --git a/docs/decisions/implemented/2026-09-21-mechanism-not-policy.zh-CN.md b/docs/decisions/implemented/2026-09-21-mechanism-not-policy.zh-CN.md new file mode 100644 index 00000000..04c3e0e0 --- /dev/null +++ b/docs/decisions/implemented/2026-09-21-mechanism-not-policy.zh-CN.md @@ -0,0 +1,95 @@ +# 机制,不是策略 + +[English](2026-09-21-mechanism-not-policy.md) + +**Status:** implemented +**Approved:** explicit +**Relates to:** [程序只回答合法性](2026-09-20-the-program-answers-legality.zh-CN.md)、[帧、它的数据格式与它的存储](../proposed/2026-09-21-the-frame-and-its-storage.zh-CN.md)、[黑板治理与能力寻址](../implemented/2026-09-06-board-governance-addressing.zh-CN.md)、[协议化协作:组成部分与空缺](../../design/protocol-governed-collaboration.zh-CN.md)、[任务单元语义](../../design/task-unit-semantics.md) + +## 问题 + +合法性与帧/存储记录需要同一条归属规则。合法性记录讲的是**程序做什么**、以及它拒绝决定什么;帧与存储记录讲的是**核心知道什么**、以及它从不解析什么。这是同一个划分,一面从决定看、一面从数据看;因为没有名字,每次出现一个新字段或一条新规则,这个划分都得重新论证一遍。 + +代价已经看得见:**"工作就是补丁"这个假设走到了六处**,其中一处是用核心自己的词汇写出来的合法性规则——`refuseWidening` 靠读 `parent.patch.editable` 来判定 permission closure,于是"关于写集"的规则被写成了"关于某个核心不该知道的工作形态"的规则。 + +而且这个划分必须锐到能**终结争论**,而不是当口号。"别把策略放进核心"这句话,并不能决定 `effect` 或 `operation` 能不能是一列,也不能决定一条合法性规则到底能不能住在程序里。 + +## 决策 + +取 Hydra 的原则——**内核提供机制、拒绝策略**——把它写成两条契约。 + +**机制契约**(核心:黑板、存储、程序): + +- 一次认领:比较交换、租约、attempt 围栏 +- 一份**不透明**声明 + 摘要 +- 一份**不透明**产物 + 摘要 +- 一个**由别人给出**的裁决 +- 生命周期:过期、回收、唤醒投递、紧凑读 +- 合法集:此刻哪些单元合法、有序、按声明的槽位预算裁剪、每个单元给理由 + +**策略契约**(核心之上:协议、计划、各 agent): + +- 单元的输入是什么、产物属于哪一类、产物怎么被判、工作怎么跑(同步还是分离式检查、可否打断、中途放弃是否安全)、以及它的并发前提 +- 合法性规则的**措辞与启用**:由协议具名、由计划声明启用 +- 选谁、按什么顺序、谁采纳、谁裁决 + +**判定程序**:对每个字段、每个类型、每条代码路径问一次——**这是机制还是策略?** 一条规则的**检查**是机制,规则的**名字、措辞与启用**是策略。 + +**可机械检查的规则**:声明的机制扫描面上,每次词汇命中都归入 `mechanism`、`policy` 或 `undecided`,附理由与精确次数。[维护的词表与分类表](../../../tools/policy-word-list.ts)拥有词汇、扫描路径和分类。词本身不决定角色:`files` 可以是票的载荷,也可以是文件系统边界。角色不等于文件。 + +[`check:policy-words`](../../../tools/policy-word-check.ts) 在共享静态契约中阻塞执行。它扫描 TypeScript 标识符、字符串和模板字面量(包括 SQL),忽略 TypeScript 注释;camel、snake、kebab 拼法都计入,但 `dispatch` 不算 `patch`。记录以仓库路径、外层具名声明/方法链(或 ``)和词为键。行号仅用于诊断,多行字面量定位到起始行;空白与注释变化不改变记录身份。`npm run check:policy-words -- --list` 输出分类站点、观察到的位置与理由。 + +既有策略命中有明确登记,不被宣称为清洁。新增站点、次数增减、失效或重复记录、无效分类、非法 TypeScript、缺失扫描路径都会使检查失败。消除泄漏需要删除或缩减相应记录;新增命中需要在同一份受审改动中明确分类并解释。检查器不自动刷新次数。加词及其理由记录在本文。 + +扫描面以维护的路径表为界,不涵盖全部源码、测试或驱动;范围内的 import 与 schema 字面量同样计入。这落地的是已批准的分类与静态检查切片(原 Plan 2–3),不是工作形态转换。帧/存储记录仍为 proposed。 + +**刻意留作待判的行**(免得被顺手定掉): + +- `task_run_tasks.effect`(声明的写集):只有当**机制必须强制写集不相交**时它才是机制;若强制是协议的义务,它就是策略数据。 +- `input` 与 `dependencies` 的粒度:依赖影响合法性与顺序,这是机制;但"输入"是携带内容还是只携带摘要,是另一个问题。 +- `operation`:可能是策略。 + +## 未完成项 + +### 工作形态边界 + +工作形态转换(原 Plan 5)不在获批切片内。以下补丁假设仍然存在;分类不等于已经实现不透明适配器边界。 + +**工作形态不是核心概念**,它是策略层对"核心不透明携带的那对声明与产物"的叫法。六处站点的分类如下。 + +| 站点 | 机制还是策略 | 归属 | +| ---------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------- | +| `BoardTicket.patch?: FrozenPatchTask & { digest }` | 策略 | 票上带一份不透明的 `declaration` 加摘要 | +| `patchFrozen()` 调 `preparePatchWork`、以及 `throw "not a patch task"` | 策略(在核心里) | 冻结与字段校验移进形态适配器;核心只交出不透明声明 | +| `refuseWidening` 读 `parent.patch.editable` | 检查是机制;措辞与启用是策略 | permission closure 变成程序**执行并给理由**的一条声明约束 | +| 会话机制的 `FrozenPatchWork` 签名 | 混合 | 把声明渲染成提示、提交产物是策略;会话生命周期、指标、取消是机制 | +| `task_run_tasks.patch_files` / `patch_editable`(store 里解析) | 策略进了 schema | 该进载荷文档;run、task、revision 是机制,input/dependencies 粒度与 operation 仍待判 | +| 驱动断言 `ticket.patch!.digest` | 策略断言 | 机制断言是:声明带摘要、认领绑 attempt、交付绑摘要 | + +今天那条合法性规则正是本文新增的那点修正的最清楚的例子:**它的检查属于程序,而 permission closure 的名字与启用属于声明。** 所以修法不是把检查搬出程序,而是**别再用核心的词汇硬编码这条规则**——这与合法性记录第 3 步(把 repair-first 从共享规划策略变成由计划声明的约束,2026-09-23 已落地)是**同一形状的修法**。两处各自独立的修法落在同一形状上,是"这个分类是对的"的证据。 + +### 字段实验与分离证据 + +- `task_run_tasks.effect`、input/dependencies 粒度与 `operation` 仍待判;其归属字段需要在另一种声明下被实际使用,再判断归属。门禁不包含这些词,也不替实验定案。 +- schema 迁移与 `TaskUnit` 中的 `files` 组标为 `undecided`:同组混有补丁专有路径与另一角色。通过拆开 DDL 主题或运行另一种输入声明来解决,不把整组改标为机制。 +- 余下 permission closure 的名字与启用耦合,需要声明约束;检查仍在程序中执行并按单元返回理由。 +- 零策略命中、第二种工作形态不改机制代码,仍是未验证的分离目标,不是门禁通过的结论。草稿的[第二形态实验](../../design/mechanism-in-the-middle.zh-CN.md#结果)仍是耦合证据,不是已完成的适配器迁移。 + +## 考虑过的替代方案 + +- **留着口号,逐案判断。** 拒绝:那正是产出六处站点的方式,没有可套用的检验。 +- **放进其中一份更窄的记录。** 拒绝:原则同时管核心可以知道什么和程序那半决定;帧或合法性记录都不拥有完整规则。 +- **核心之上建插件框架。** 拒绝:一个默认适配器加另一种形态就能检验边界,不需要注册表框架。 +- **凭偏好决定待判行。** 拒绝:字段实验仍是定案证据。 +- **立即拒绝所有既有策略命中。** 拒绝:获批切片登记债务,不授权一次性重写工作形态。 +- **按文件豁免某词,或只限制次数上限。** 拒绝:会静默接纳替换站点或留下失效豁免。具名作用域精确次数让移动和缩减进入审查。 + +## 后果 + +- **把核心做空。** Hydra 的教训是双向的:一个不带默认策略的内核不可用。务实形式是核心**可以带一个默认策略**(补丁工作),但**不得要求**它。 +- **策略词清单会僵化。** 同一个词在一处是机制、在另一处是策略,所以检查要带路径而不只是词,而清单必须接受加词。 +- **待判的行可能永久待判。** 一行标着待判却没有实验在背后,就是一个没人声明的策略。 +- **某个策略词可能合理地再留一个版本。** 本文不要求一次性重命名;它要求每一处残留都被**列出来**。 +- **门禁可以被绕过。** 同义词、同一具名作用域内次数不变的替换,都可能绕过检查。分类和扫描面变化需要审查;检查是地板,不是已经分离的证明。 +- **精确次数有维护成本。** 有意移动或消除代码需要更新表;它防止静默增长,不禁止经过审查的策略变动。 +- 合法性与帧/存储记录指向本文,共用这一归属规则;链接不批准独立的帧/存储迁移。 diff --git a/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.md b/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.md index d71c508b..166ba6bc 100644 --- a/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.md +++ b/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.md @@ -4,7 +4,7 @@ **Status:** implemented **Approved:** explicit -**Relates to:** [Tests do not need a filesystem](2026-09-20-tests-need-no-filesystem.md), [The checks read a live mutant](../../postmortem/0003-checks-read-a-live-mutant.md), [Bound agent verification as one run](2026-09-23-verification-whole-run-deadline.md), [The contract's obligations](../../design/task-unit-semantics-obligations.md), [Mechanism, not policy](../proposed/2026-09-21-mechanism-not-policy.md) +**Relates to:** [Tests do not need a filesystem](2026-09-20-tests-need-no-filesystem.md), [The checks read a live mutant](../../postmortem/0003-checks-read-a-live-mutant.md), [Bound agent verification as one run](2026-09-23-verification-whole-run-deadline.md), [The contract's obligations](../../design/task-unit-semantics-obligations.md), [Mechanism, not policy](2026-09-21-mechanism-not-policy.md) ## Problem @@ -212,7 +212,7 @@ that names the rule" is a defect this register reports and none of those tools w - **Insert the mutation into the code behind a runtime switch** (mutant schemata, `mutation_active("...")` guards). Rejected for this repository: the guard is real code in `src/`, and a switched-off mutation path is a policy word in the mechanism layer, which [mechanism, not - policy](../proposed/2026-09-21-mechanism-not-policy.md) forbids. + policy](2026-09-21-mechanism-not-policy.md) forbids. - **Auto-generate mutants from operators over the whole tree** (what pitest, StrykerJS and cargo-mutants do). Rejected as the form here: it would replace named evidence with a score, and the ledger needs a named tooth per row. The derived form keeps the operator idea and the naming. diff --git a/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.zh-CN.md b/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.zh-CN.md index 43761a32..a30a41fd 100644 --- a/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.zh-CN.md +++ b/docs/decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.zh-CN.md @@ -4,7 +4,7 @@ **Status:** implemented **Approved:** explicit -**Relates to:** [测试不需要文件系统](2026-09-20-tests-need-no-filesystem.md)、[检查读到的是一个还活着的 mutant](../../postmortem/0003-checks-read-a-live-mutant.md)、[把 agent 验证限制为一次运行](2026-09-23-verification-whole-run-deadline.md)、[契约的义务](../../design/task-unit-semantics-obligations.md)、[机制,而非策略](../proposed/2026-09-21-mechanism-not-policy.md) +**Relates to:** [测试不需要文件系统](2026-09-20-tests-need-no-filesystem.md)、[检查读到的是一个还活着的 mutant](../../postmortem/0003-checks-read-a-live-mutant.md)、[把 agent 验证限制为一次运行](2026-09-23-verification-whole-run-deadline.md)、[契约的义务](../../design/task-unit-semantics-obligations.md)、[机制,而非策略](2026-09-21-mechanism-not-policy.zh-CN.md) ## 问题 @@ -91,7 +91,7 @@ - **继续手写锚点、只是写得更小。**作为整体答案被否:那是纪律而不是机制,而纪律正是 52% 的牙当时违反的东西。它作为"替换字节"的规则保留下来,并且是刻意最小:不要消息文本、不要兄弟实参、能用一项时不要整条语句。 - **只按 AST 节点锚定,替换字节照旧**(早先的 `ast.within` 加一对文本)。被否,理由是实测:149 颗里已有 71 颗是这个形态,而本轮仍有两颗死在它上面——因为**替换内容**和**片段**仍是对行的拷贝。 - **整体改用输入侧检查替代 mutant。**按上面的测量被否:它没有区分力(每一颗被抓到的 mutant 从外部都可见),而且会丢掉唯一一种能谈"预料之外的实现疏忽"而非"已声明违规"的证据。 -- **把变异插进代码、放在运行时开关之后**(mutant schemata、`mutation_active("...")` 守卫)。本仓库否掉:那个守卫是 `src/` 里的真实代码,而"被关掉的变异路径"是机制层里的策略词,[机制,而非策略](../proposed/2026-09-21-mechanism-not-policy.md)禁止这样做。 +- **把变异插进代码、放在运行时开关之后**(mutant schemata、`mutation_active("...")` 守卫)。本仓库否掉:那个守卫是 `src/` 里的真实代码,而"被关掉的变异路径"是机制层里的策略词,[机制,而非策略](2026-09-21-mechanism-not-policy.zh-CN.md)禁止这样做。 - **对整个语法树按算子自动生成 mutant**(pitest、StrykerJS、cargo-mutants 的做法)。作为本仓库的形式被否:它会把具名证据换成一个分数,而账本每一行都需要一颗具名的牙。derived 形态保留了算子这个想法和"具名"。 - **干脆不进任何闸门、只靠常设规则。**被否:本记录的实测就是"没人注意到两颗牙已经停摆",而"依赖被记住"的规则正是本仓库在别处已经替换掉的形状。 diff --git a/docs/decisions/proposed/2026-09-21-mechanism-not-policy.zh-CN.md b/docs/decisions/proposed/2026-09-21-mechanism-not-policy.zh-CN.md deleted file mode 100644 index c2d70d13..00000000 --- a/docs/decisions/proposed/2026-09-21-mechanism-not-policy.zh-CN.md +++ /dev/null @@ -1,91 +0,0 @@ -# 机制,不是策略 - -[English](2026-09-21-mechanism-not-policy.md) - -**Status:** proposed -**Relates to:** [程序只回答合法性](../implemented/2026-09-20-the-program-answers-legality.zh-CN.md)、[帧、它的数据格式与它的存储](2026-09-21-the-frame-and-its-storage.zh-CN.md)、[黑板治理与能力寻址](../implemented/2026-09-06-board-governance-addressing.zh-CN.md)、[协议化协作:组成部分与空缺](../../design/protocol-governed-collaboration.zh-CN.md)、[任务单元语义](../../design/task-unit-semantics.md) - -## 问题 - -两份提案各需要同一句话,而谁都没把它说出来。合法性记录讲的是**程序做什么**、以及它拒绝决定什么;帧与存储记录讲的是**核心知道什么**、以及它从不解析什么。这是同一个划分,一面从决定看、一面从数据看;因为没有名字,每次出现一个新字段或一条新规则,这个划分都得重新论证一遍。 - -代价已经看得见:**"工作就是补丁"这个假设走到了六处**,其中一处是用核心自己的词汇写出来的合法性规则——`refuseWidening` 靠读 `parent.patch.editable` 来判定 permission closure,于是"关于写集"的规则被写成了"关于某个核心不该知道的工作形态"的规则。 - -而且这个划分必须锐到能**终结争论**,而不是当口号。"别把策略放进核心"这句话,并不能决定 `effect` 或 `operation` 能不能是一列,也不能决定一条合法性规则到底能不能住在程序里。 - -## 提案 - -取 Hydra 的原则——**内核提供机制、拒绝策略**——把它写成两条契约。 - -**机制契约**(核心:黑板、存储、程序): - -- 一次认领:比较交换、租约、attempt 围栏 -- 一份**不透明**声明 + 摘要 -- 一份**不透明**产物 + 摘要 -- 一个**由别人给出**的裁决 -- 生命周期:过期、回收、唤醒投递、紧凑读 -- 合法集:此刻哪些单元合法、有序、按声明的槽位预算裁剪、每个单元给理由 - -**策略契约**(核心之上:协议、计划、各 agent): - -- 单元的输入是什么、产物属于哪一类、产物怎么被判、工作怎么跑(同步还是分离式检查、可否打断、中途放弃是否安全)、以及它的并发前提 -- 合法性规则的**措辞与启用**:由协议具名、由计划声明启用 -- 选谁、按什么顺序、谁采纳、谁裁决 - -**判定程序**:对每个字段、每个类型、每条代码路径问一次——**这是机制还是策略?** 一条规则的**检查**是机制,规则的**名字、措辞与启用**是策略。 - -**可机械检查的规则**:**机制层(核心与程序)的代码、类型声明与 schema 路径里不得出现策略词汇。** 清单是维护的——`patch`、`editable`、`files`、`instruction`、`checks`、`repair-first`——命中即泄漏,不是风格问题。清单可以加词,加词要记录。光看词不足以判定:`files` 在票的类型里是策略词,在文件系统边界上是机制词,所以检查要带上**路径**。 - -**刻意留作待判的行**(免得被顺手定掉): - -- `task_run_tasks.effect`(声明的写集):只有当**机制必须强制写集不相交**时它才是机制;若强制是协议的义务,它就是策略数据。 -- `input` 与 `dependencies` 的粒度:依赖影响合法性与顺序,这是机制;但"输入"是携带内容还是只携带摘要,是另一个问题。 -- `operation`:可能是策略。 - -## 这给"工作形态"定了位 - -**工作形态不是核心概念**,它是策略层对"核心不透明携带的那对声明与产物"的叫法。六处站点的分类如下。 - -| 站点 | 机制还是策略 | 归属 | -| ---------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------- | -| `BoardTicket.patch?: FrozenPatchTask & { digest }` | 策略 | 票上带一份不透明的 `declaration` 加摘要 | -| `patchFrozen()` 调 `preparePatchWork`、以及 `throw "not a patch task"` | 策略(在核心里) | 冻结与字段校验移进形态适配器;核心只交出不透明声明 | -| `refuseWidening` 读 `parent.patch.editable` | 检查是机制;措辞与启用是策略 | permission closure 变成程序**执行并给理由**的一条声明约束 | -| 会话机制里十处 `FrozenPatchWork` | 混合 | 把声明渲染成提示、提交产物是策略;会话生命周期、指标、取消是机制 | -| `task_run_tasks.patch_files` / `patch_editable`(store 里解析) | 策略进了 schema | 该进载荷文档;机制部分是 run、task、revision、input、dependencies、operation 那几列 | -| 驱动断言 `ticket.patch!.digest` | 策略断言 | 机制断言是:声明带摘要、认领绑 attempt、交付绑摘要 | - -今天那条合法性规则正是本文新增的那点修正的最清楚的例子:**它的检查属于程序,而 permission closure 的名字与启用属于声明。** 所以修法不是把检查搬出程序,而是**别再用核心的词汇硬编码这条规则**——这与合法性记录第 3 步(把 repair-first 从共享规划策略变成由计划声明的约束,2026-09-23 已落地)是**同一形状的修法**。两处各自独立的修法落在同一形状上,是"这个分类是对的"的证据。 - -## 考虑过的替代方案 - -- **留着口号,逐案判断。** 拒绝:那就正是产出六处站点的方式,而且它不提供可以套用的检验。 -- **把这条原则放进帧记录。** 拒绝:原则比帧更宽——它也管程序那半决定——那样帧记录会变成"关于程序的规则"的拥有者。 -- **放进合法性记录。** 反向的同样理由拒绝:那份记录更窄(讲程序的决定),而"核心可以知道什么"不是合法性问题的。 -- **核心之上搞一个带注册表的插件框架。** 拒绝:还没有第二个策略来支撑这套机械;一个默认适配器加另一个形态就足以检验这条缝。 -- **现在就定掉待判的行。** 拒绝:凭偏好挑一边,正是本文要用证据替换掉的做法。 - -## 验收标准 - -- 分类覆盖所有出现策略词汇的站点,每行标为机制、策略或待判。 -- 机制层的读写路径、类型声明与 schema 路径里不出现策略词汇(清单维护,机械检查)。 -- 合法性规则的名字与启用来自声明,检查在程序里跑,并按单元返回理由。 -- 增加第二个工作形态不改变任何机制代码;用数据式检查运行器已经在做的"值工作"演示。 -- 每一行待判都标为待判,并且由**实验**而不是偏好来定。 -- 合法性记录与帧记录都指到这里,让这个划分只有一个出处。 - -## 风险 - -- **把核心做空。** Hydra 的教训是双向的:一个不带默认策略的内核不可用。务实形式是核心**可以带一个默认策略**(补丁工作),但**不得要求**它。 -- **策略词清单会僵化。** 同一个词在一处是机制、在另一处是策略,所以检查要带路径而不只是词,而清单必须接受加词。 -- **待判的行可能永久待判。** 一行标着待判却没有实验在背后,就是一个没人声明的策略。 -- **某个策略词可能合理地再留一个版本。** 本文不要求一次性重命名;它要求每一处残留都被**列出来**。 -- **grep 检查可以被同义词绕过。** 检查是地板,不是"已经分离"的证明。 - -## 计划 - -1. 本文。不动代码。 -2. 分类表,标出待判的行。 -3. 那条检查:维护的策略词清单加一个脚本,接进静态集合。 -4. 两份记录都指过来。 -5. 然后才是工作形态切片:机制侧携带不透明声明,`patch` 作为默认适配器而不再是类型。 diff --git a/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.md b/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.md index 2c426f3c..28b126e2 100644 --- a/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.md +++ b/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.md @@ -3,7 +3,7 @@ [中文](2026-09-21-the-frame-and-its-storage.zh-CN.md) **Status:** proposed -**Relates to:** [Mechanism, not policy](2026-09-21-mechanism-not-policy.md), [The program answers legality](../implemented/2026-09-20-the-program-answers-legality.md), [Name the collaboration protocol and its task-unit sub-protocol](../implemented/2026-09-20-name-the-collaboration-protocol.md), [Protocol-governed collaboration: the parts, the gaps](../../design/protocol-governed-collaboration.md), [Board governance and capability addressing](../implemented/2026-09-06-board-governance-addressing.md), [Task unit semantics](../../design/task-unit-semantics.md) +**Relates to:** [Mechanism, not policy](../implemented/2026-09-21-mechanism-not-policy.md), [The program answers legality](../implemented/2026-09-20-the-program-answers-legality.md), [Name the collaboration protocol and its task-unit sub-protocol](../implemented/2026-09-20-name-the-collaboration-protocol.md), [Protocol-governed collaboration: the parts, the gaps](../../design/protocol-governed-collaboration.md), [Board governance and capability addressing](../implemented/2026-09-06-board-governance-addressing.md), [Task unit semantics](../../design/task-unit-semantics.md) ## Problem diff --git a/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.zh-CN.md b/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.zh-CN.md index 2df24bf3..1b408d5f 100644 --- a/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.zh-CN.md +++ b/docs/decisions/proposed/2026-09-21-the-frame-and-its-storage.zh-CN.md @@ -3,7 +3,7 @@ [English](2026-09-21-the-frame-and-its-storage.md) **Status:** proposed -**Relates to:** [机制,不是策略](2026-09-21-mechanism-not-policy.zh-CN.md)、[程序只回答合法性](../implemented/2026-09-20-the-program-answers-legality.zh-CN.md)、[给协作协议及其任务单元子协议命名](../implemented/2026-09-20-name-the-collaboration-protocol.zh-CN.md)、[协议化协作:组成部分与空缺](../../design/protocol-governed-collaboration.zh-CN.md)、[黑板治理与能力寻址](../implemented/2026-09-06-board-governance-addressing.zh-CN.md)、[任务单元语义](../../design/task-unit-semantics.md) +**Relates to:** [机制,不是策略](../implemented/2026-09-21-mechanism-not-policy.zh-CN.md)、[程序只回答合法性](../implemented/2026-09-20-the-program-answers-legality.zh-CN.md)、[给协作协议及其任务单元子协议命名](../implemented/2026-09-20-name-the-collaboration-protocol.zh-CN.md)、[协议化协作:组成部分与空缺](../../design/protocol-governed-collaboration.zh-CN.md)、[黑板治理与能力寻址](../implemented/2026-09-06-board-governance-addressing.zh-CN.md)、[任务单元语义](../../design/task-unit-semantics.md) ## 问题 diff --git a/docs/design/ci-cd-and-quality.md b/docs/design/ci-cd-and-quality.md index 13e86ef2..11747117 100644 --- a/docs/design/ci-cd-and-quality.md +++ b/docs/design/ci-cd-and-quality.md @@ -61,6 +61,8 @@ exit_criteria: Replace with a stable contract test or remove after the redesign `npm run check:tests`(`tsc -p tsconfig.tests.json --noUnusedLocals --noUnusedParameters`)进入 `verify:static` 与 `ci-and-tests` 的阻塞集合。它检查 `tests/`、配置显式包含的源文件以及它们导入的依赖;不声明覆盖全部 `evals/`、`scripts/`、`tools/`。 +`check:policy-words` 在 `verify:static` 与 `ci-and-tests` 的阻塞集合中执行一次;维护词表、分类记录和扫描面由[机制,不是策略](../decisions/implemented/2026-09-21-mechanism-not-policy.zh-CN.md#决策)拥有。它阻止未登记的命中与记录漂移,不证明机制层已无策略。 + `verify:static` 中的 `mutation:anchors`(`tools/mutation-teeth.ts --anchors-only`)只做一件事:把 110 颗具名 mutant 的位置全部解析一遍,不跑任何用例、不写任何字节,在 1 秒内回答“每一颗牙是否还瞄着东西”。它进入静态契约是因为**一颗锚点失效时没有别的检查会注意到**:全量 sweep 不跑(`mutation:teeth` 不在任何 CI 作业里),而一颗匹配不到位置的牙在 sweep 报告里只是“不可应用”并被排除出分母——本轮修掉的两颗牙就是这样悄无声息地停摆的。全量 sweep 仍然不进闸门:它是分钟级、要跑用例,属于推送前的常设规则([决策](../decisions/implemented/2026-09-24-mutants-are-derived-not-anchored.md))。 `verify:static` 中的 `complexity:gate` 默认以 `git merge-base HEAD origin/main` 为基线(可用 `--base ` 显式覆盖)。基线必须是 merge base 而不是 `HEAD`:后者只比较未提交的工作树,于是已提交到分支的改动完全不可见 —— 在 CI 的干净检出上它永远报“无改动”,等于每个 PR 都没有被这条 gate 检查过。因此每次运行都会**陈述自己用了哪个基线**,并**点名它未能测量的改动文件**(ESLint 拒绝某路径、或文件根本无法解析,都会产出“零发现”,与“量过且干净”无法区分)。 diff --git a/docs/design/mechanism-in-the-middle.md b/docs/design/mechanism-in-the-middle.md index 0c714ab5..41785f6e 100644 --- a/docs/design/mechanism-in-the-middle.md +++ b/docs/design/mechanism-in-the-middle.md @@ -7,7 +7,7 @@ A model of how a board entry travels, and the checks that decide whether the model earns a place in the records. This document owns the model, the rule that says when a boundary earns a seam, and the checks. It does not restate the parts inventory, which lives in [protocol-governed-collaboration.md](protocol-governed-collaboration.md), -nor the decisions, which live in three records: [mechanism, not policy](../decisions/proposed/2026-09-21-mechanism-not-policy.md), +nor the decisions, which live in three records: [mechanism, not policy](../decisions/implemented/2026-09-21-mechanism-not-policy.md), [the frame and its storage](../decisions/proposed/2026-09-21-the-frame-and-its-storage.md), and [the program answers legality](../decisions/implemented/2026-09-20-the-program-answers-legality.md). @@ -139,6 +139,9 @@ out in its favour. If they do not, this document is archived rather than promote ## The checks +The continuous classification check is owned by [mechanism, not policy](../decisions/implemented/2026-09-21-mechanism-not-policy.md#decision). +The measurements below are historical experiments, not the maintained inventory or proof of separation. + Predictions are recorded before the measurement, so a surprise is visible rather than rationalised. **(a) Policy words in the middle.** Grep the middle layer - `src/core/store/`, `src/integration/ooo-board.ts`, @@ -183,8 +186,8 @@ concentration is where it was predicted: `src/integration/ooo-board.ts` carries The measurement forced two corrections. First, the raw count overstates the case, which is the caveat this check was written with: `files` in `src/core/store/writes.ts` and `src/core/store/retrieval.ts` is a mechanism word - a path inside a store - and `checks` in `src/integration/ooo-candidate.ts` names the check _runner_, -which is mechanism too. The word list should therefore drop `files` and `checks` and keep `patch`, `editable` -and `instruction`. Second, what remains is still about 120 hits, so "the middle is policy-free" is false as a +which is mechanism too. That measurement suggested dropping `files` and `checks`; the maintained check +instead retains context-dependent words and classifies their sites under the decision linked above. Second, what remains is still about 120 hits, so "the middle is policy-free" is false as a description of today, at a scale an order of magnitude past the prediction. What the model has is directional support: the leak is real, large, and concentrated in three files, which is exactly what a seam would have to remove. diff --git a/docs/design/mechanism-in-the-middle.zh-CN.md b/docs/design/mechanism-in-the-middle.zh-CN.md index 536cd545..159ac068 100644 --- a/docs/design/mechanism-in-the-middle.zh-CN.md +++ b/docs/design/mechanism-in-the-middle.zh-CN.md @@ -4,7 +4,7 @@ **Created:** 2026-09-21 **Updated:** 2026-09-21 -一个黑板条目怎么走,以及决定这个模型有没有资格进记录的那几项检查。本文拥有模型、"一条边界什么时候够资格建缝"的那条规则、以及这些检查。它不复述大类清单(那在 [protocol-governed-collaboration.md](protocol-governed-collaboration.zh-CN.md)),也不复述决策(那在三份记录里:[机制,不是策略](../decisions/proposed/2026-09-21-mechanism-not-policy.zh-CN.md)、[帧、它的数据格式与它的存储](../decisions/proposed/2026-09-21-the-frame-and-its-storage.zh-CN.md)、[程序只回答合法性](../decisions/implemented/2026-09-20-the-program-answers-legality.zh-CN.md))。 +一个黑板条目怎么走,以及决定这个模型有没有资格进记录的那几项检查。本文拥有模型、"一条边界什么时候够资格建缝"的那条规则、以及这些检查。它不复述大类清单(那在 [protocol-governed-collaboration.md](protocol-governed-collaboration.zh-CN.md)),也不复述决策(那在三份记录里:[机制,不是策略](../decisions/implemented/2026-09-21-mechanism-not-policy.zh-CN.md)、[帧、它的数据格式与它的存储](../decisions/proposed/2026-09-21-the-frame-and-its-storage.zh-CN.md)、[程序只回答合法性](../decisions/implemented/2026-09-20-the-program-answers-legality.zh-CN.md))。 ## 模型 @@ -70,6 +70,8 @@ ## 检查 +持续分类检查由[机制,不是策略](../decisions/implemented/2026-09-21-mechanism-not-policy.zh-CN.md#决策)拥有。下方测量是历史实验,不是维护的清单,也不是已经分离的证明。 + 预测先写下来再测量,这样"意外"是可见的,而不是事后被圆过去的。 **(a)中间层里的策略词。** 对中间层——`src/core/store/`、`src/integration/ooo-board.ts`、`src/integration/ooo-dispatch.ts`、`src/integration/task-semantics.ts`、`src/integration/ooo-execution.ts`、`src/integration/ooo-candidate.ts`——grep 这份策略词清单:`patch`、`editable`、`instruction`、`checks`、`files`、`repair-first`。**每一处命中都要带路径列出,不能只给计数**,因为同一个词在一条路径上是机制、在另一条路径上是策略。预测:十五处以上,集中在板子与合法性模块。预期的发现:板子自己的票类型与那个冻结方法就是中心。 @@ -91,7 +93,7 @@ **(a)中间层里的策略词。** 预测十五处以上。实测六个文件里共 195 处:`patch` 106、`files` 39、`checks` 23、`editable` 14、`instruction` 12、`repair-first` 1。集中处与预测一致:`src/integration/ooo-board.ts` 带 53 处 `patch`,`src/integration/task-semantics.ts` 27 处 `patch` 与 9 处 `editable`,`src/core/store/base.ts` 13 处 `patch`。 -测量迫使两条修正。**第一,原始计数高估了情况**——而这正是写这条检查时要带上的那个前提:`src/core/store/writes.ts` 与 `src/core/store/retrieval.ts` 里的 `files` 是机制词(存储里的路径),`src/integration/ooo-candidate.ts` 里的 `checks` 指的是检查的**运行器**,也是机制。所以词表应**去掉 `files` 与 `checks`**,只留 `patch`、`editable`、`instruction`。**第二,剩下的仍有约 120 处**,所以「中间层是无策略的」作为**今天的描述**是假的,而且超出一个数量级。模型拿到的是**方向性支持**:泄漏真实、量大、集中在三个文件里——那正是接上一条缝要拿掉的东西。 +测量迫使两条修正。**第一,原始计数高估了情况**——而这正是写这条检查时要带上的那个前提:`src/core/store/writes.ts` 与 `src/core/store/retrieval.ts` 里的 `files` 是机制词(存储里的路径),`src/integration/ooo-candidate.ts` 里的 `checks` 指的是检查的**运行器**,也是机制。那次测量建议去掉 `files` 与 `checks`;维护的检查依据上方决策,保留依赖语境的词并按站点分类。**第二,剩下的仍有约 120 处**,所以「中间层是无策略的」作为**今天的描述**是假的,而且超出一个数量级。模型拿到的是**方向性支持**:泄漏真实、量大、集中在三个文件里——那正是接上一条缝要拿掉的东西。 **(b)两端。** 生产端预测「集中且可分」,实测三到六个函数散在三个文件:`src/integration/ooo-patch.ts` 里的 `preparePatchWork`、`patchPrompt`、`patchCandidate`、`patchSubmission`,`src/integration/ooo-session-mechanism.ts` 里的 `snapshotText`,`evals/ooo-execution/data-check-runner.ts` 里的 `runTestFile`。预测成立。呈现端预测「散落、没有缝」,实测六个站点:`src/core/store/base.ts` 的预览文本、`src/core/types.ts` 的条目与预览类型、`src/cli/protocol.ts` 的线上形状、`src/cli/service.ts` 的服务、`.pi/extensions/nmg/index.ts` 里面向 agent 的渲染、以及由 `src/prompts/nmg-prompts.yaml` 生成的工具描述。这条预测也成立,但有**一处对本文的修正**:黑板条目只有一种呈现,而旧表里的分层词汇是从记忆那一侧**借**来的,不是黑板这一侧**找到**的。对同一批站点后来的一遍检查看到计数看不到的东西:那六个站点是一条**链**,不是六个格式化器,而重复就坐在其中三处——store 的预览规则、适配器的通用助手、以及适配器广播路径里的那条裸切。 diff --git a/package.json b/package.json index 32686f7f..4e21ee42 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "mutation:anchors": "node --experimental-strip-types tools/mutation-teeth.ts --anchors-only", "verify:packages": "node --experimental-strip-types tools/verify-packages.ts", "check:lock": "node --experimental-strip-types tools/check-lock.ts", - "verify:static": "npm run build && npm run package:check && npm run check && npm run check:tests && npm run mutation:anchors && npm run check:lock && npm run lint && npm run format:check && npm run docs:check && npm run agent:context:check && npm run complexity:gate && npm run verify:packages && npm run glossary:check && npm run rtm:check", + "verify:static": "npm run build && npm run package:check && npm run check && npm run check:tests && npm run mutation:anchors && npm run check:lock && npm run lint && npm run format:check && npm run docs:check && npm run agent:context:check && npm run complexity:gate && npm run verify:packages && npm run glossary:check && npm run check:policy-words && npm run rtm:check", "verify:product-ci": "npm run build && npm run test:coverage", "verify:research": "npm run prompts:generate && npm run test:research", "verify:node-compat": "npm run build && npm run check && npm run package:check", @@ -142,6 +142,7 @@ "test:chaos": "npm run build && node --experimental-strip-types --test \"tests/chaos/*.test.ts\"", "test:cg": "node --experimental-strip-types --test tests/core/autodiff.test.ts tests/core/differentiable-controller.test.ts tests/core/hierarchical-activation.test.ts tests/core/memory-graph-reasoner.test.ts tests/core/fork-merge.test.ts tests/core/robustness.test.ts", "glossary:check": "node --experimental-strip-types tools/glossary-check.ts", + "check:policy-words": "node --experimental-strip-types tools/policy-word-check.ts", "rtm:check": "node --experimental-strip-types tools/rtm-check.ts" }, "devDependencies": { diff --git a/tests/tools/policy-word-check.test.ts b/tests/tools/policy-word-check.test.ts new file mode 100644 index 00000000..6a782b03 --- /dev/null +++ b/tests/tools/policy-word-check.test.ts @@ -0,0 +1,110 @@ +import assert from "node:assert/strict"; +import { resolve } from "node:path"; +import test from "node:test"; + +import { + collectPolicyWords, + comparePolicyWords, + inspectMechanism, + policyWordsIn, +} from "../../tools/policy-word-check.ts"; +import { POLICY_WORDS, type ClassifiedSite } from "../../tools/policy-word-list.ts"; + +function site(overrides: Partial = {}): ClassifiedSite { + return { + path: "src/core/store/example.ts", + scope: "record", + word: "patch", + count: 1, + classification: "policy", + reason: "Existing shape assumption, not a mechanism.", + ...overrides, + }; +} + +test("camel, snake and kebab spellings are visible without counting dispatch as patch", () => { + assert.deepEqual( + policyWordsIn("patch_files preparePatchWork FrozenPatchWork PATCH_FILES", POLICY_WORDS), + ["patch", "patch", "patch", "patch", "files", "files"], + ); + assert.deepEqual(policyWordsIn("dispatch DispatchBoard dispatched", POLICY_WORDS), []); + assert.deepEqual(policyWordsIn("repair-first repair_first repairFirst", POLICY_WORDS), [ + "repair-first", + "repair-first", + "repair-first", + ]); +}); + +test("types, member accesses, SQL templates and runtime strings count; comments do not", () => { + const hits = collectPolicyWords( + "unit.ts", + `// patch files instruction + interface Ticket { patch?: FrozenPatchWork } + function record() { const sql = \`patch_files TEXT, patch_editable TEXT\`; + return parent.patch.editable + "not a patch task"; } + `, + ); + assert.equal(hits.filter((hit) => hit.word === "patch").length, 6); + assert.ok(hits.some((hit) => hit.scope === "Ticket" && hit.word === "patch")); + assert.ok(hits.some((hit) => hit.scope === "record" && hit.word === "editable")); + assert.ok(hits.every((hit) => hit.line > 1)); +}); + +test("a new policy word site is refused with its path and position", () => { + const hits = collectPolicyWords( + "src/core/store/example.ts", + "function record() { return ticket.patch; }", + ); + assert.match( + comparePolicyWords(hits, [])[0]!, + /example\.ts#record:patch: unclassified.*lines 1/u, + ); + assert.deepEqual(comparePolicyWords(hits, [site()]), []); + const doubled = collectPolicyWords( + "src/core/store/example.ts", + "function record() { return ticket.patch || parent.patch; }", + ); + assert.match(comparePolicyWords(doubled, [site()])[0]!, /classified 1, found 2/u); +}); + +test("removing a leak requires retiring the classification rather than leaving dead exemptions", () => { + assert.match(comparePolicyWords([], [site()])[0]!, /classified 1, found 0/u); +}); + +test("a same-count move to another member is not silently grandfathered", () => { + const hits = collectPolicyWords( + "src/core/store/example.ts", + "function another() { return ticket.patch; }", + ); + assert.equal(comparePolicyWords(hits, [site()]).length, 2); +}); + +test("class methods get distinct scope identities; whitespace and comment changes do not break them", () => { + const collect = (text: string) => + collectPolicyWords("unit.ts", text).map(({ path, scope, word }) => ({ path, scope, word })); + assert.deepEqual( + collect("class Board { freeze() { return task.patch; } }"), + collect("class Board {\n // patch\n freeze() { return task.patch; }\n }"), + ); + assert.equal( + collect("class Board { freeze() { return task.patch; } }")[0]!.scope, + "Board.freeze", + ); +}); + +test("malformed TypeScript, invalid classifications and duplicate entries fail closed", () => { + assert.throws(() => collectPolicyWords("broken.ts", "function broken( {"), /cannot inspect/u); + assert.match(comparePolicyWords([], [site({ reason: "" })])[0]!, /invalid classification/u); + assert.ok( + comparePolicyWords([], [site(), site()]).some((error) => + /duplicate classification/u.test(error), + ), + ); +}); + +test("the repository's maintained table classifies every occurrence on the declared surface", () => { + const report = inspectMechanism(resolve(import.meta.dirname, "../..")); + assert.ok(report.files > 0); + assert.ok(report.hits.length > 0); + assert.deepEqual(report.errors, []); +}); diff --git a/tests/tools/test-groups.test.ts b/tests/tools/test-groups.test.ts index 3aef99f8..bc78e413 100644 --- a/tests/tools/test-groups.test.ts +++ b/tests/tools/test-groups.test.ts @@ -90,6 +90,17 @@ test("tests-surface type checking is blocking in the shared static contract", () ); }); +test("the maintained policy-word check blocks the shared static contract exactly once", () => { + const checks = [...packageJson.scripts["verify:static"]!.matchAll(/npm run ([\w:-]+)/gu)].map( + (match) => match[1]!, + ); + assert.equal(checks.filter((name) => name === "check:policy-words").length, 1); + assert.equal( + packageJson.scripts["check:policy-words"], + "node --experimental-strip-types tools/policy-word-check.ts", + ); +}); + test("agent static checks match the CI contract without nested duplicate execution", () => { const context = parseYaml( readFileSync(new URL("../../agent-context.yaml", import.meta.url), "utf8"), diff --git a/tools/policy-word-check.ts b/tools/policy-word-check.ts new file mode 100644 index 00000000..c1ae188d --- /dev/null +++ b/tools/policy-word-check.ts @@ -0,0 +1,179 @@ +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import ts from "typescript"; + +import { CLASSIFICATIONS, MECHANISM_PATHS, POLICY_WORDS } from "./policy-word-list.ts"; +import type { ClassifiedSite } from "./policy-word-list.ts"; + +export interface PolicyWordHit { + path: string; + scope: string; + word: string; + line: number; +} + +/** Match snake/kebab/camel names as words, not `patch` inside `dispatch`. */ +export function policyWordsIn(text: string, words: readonly string[]): string[] { + const separated = text + .replace(/([a-z0-9])([A-Z])/gu, "$1 $2") + .replace(/([A-Z])([A-Z][a-z])/gu, "$1 $2") + .toLowerCase(); + return words.flatMap((word) => { + const escaped = word.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); + const spelling = escaped.split("-").join("[\\s_-]+"); + const pattern = new RegExp(`(? word); + }); +} + +function scopeName(node: ts.Node): string | undefined { + if (ts.isConstructorDeclaration(node)) return "constructor"; + if ( + ts.isFunctionDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isInterfaceDeclaration(node) || + ts.isTypeAliasDeclaration(node) || + ts.isClassDeclaration(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) + ) + return node.name?.getText(); + return undefined; +} + +function literalText(node: ts.Node): string | undefined { + if ( + ts.isIdentifier(node) || + ts.isPrivateIdentifier(node) || + ts.isStringLiteral(node) || + ts.isNoSubstitutionTemplateLiteral(node) || + ts.isTemplateHead(node) || + ts.isTemplateMiddle(node) || + ts.isTemplateTail(node) + ) + return node.text; + return undefined; +} + +/** Comments are not code; SQL and runtime text inside literals are inspected. */ +export function collectPolicyWords( + path: string, + text: string, + words = POLICY_WORDS, +): PolicyWordHit[] { + const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true); + // Parse failures must not become an empty (passing) inventory. + const diagnostics: readonly ts.Diagnostic[] = ( + source as ts.SourceFile & { + parseDiagnostics: readonly ts.Diagnostic[]; + } + ).parseDiagnostics; + if (diagnostics.length) throw new Error(`${path}: cannot inspect invalid TypeScript`); + const hits: PolicyWordHit[] = []; + function visit(node: ts.Node, parents: readonly string[]): void { + const name = scopeName(node); + const scopes = name ? [...parents, name] : parents; + const value = literalText(node); + if (value !== undefined) { + const line = source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1; + for (const word of policyWordsIn(value, words)) { + hits.push({ path, scope: scopes.join(".") || "", word, line }); + } + } + ts.forEachChild(node, (child) => visit(child, scopes)); + } + visit(source, []); + return hits; +} + +function siteKey(site: Pick): string { + return `${site.path}#${site.scope}:${site.word}`; +} + +function validClassification(site: ClassifiedSite): boolean { + return ( + !!site.reason.trim() && + ["mechanism", "policy", "undecided"].includes(site.classification) && + Number.isInteger(site.count) && + site.count > 0 + ); +} + +export function comparePolicyWords( + hits: readonly PolicyWordHit[], + sites: readonly ClassifiedSite[], +): string[] { + const actual = new Map(); + for (const hit of hits) { + const key = siteKey(hit); + const group = actual.get(key) ?? []; + group.push(hit); + actual.set(key, group); + } + const errors: string[] = []; + const seen = new Set(); + for (const site of sites) { + const key = siteKey(site); + if (seen.has(key)) errors.push(`${key}: duplicate classification`); + seen.add(key); + if (!validClassification(site)) errors.push(`${key}: invalid classification`); + const group = actual.get(key) ?? []; + if (group.length !== site.count) { + errors.push( + `${key}: classified ${site.count}, found ${group.length} (lines ${group.map((hit) => hit.line).join(", ") || "none"})`, + ); + } + actual.delete(key); + } + for (const [key, group] of actual) + errors.push( + `${key}: unclassified (${group.length} at lines ${group.map((hit) => hit.line).join(", ")})`, + ); + return errors; +} + +function sourceFiles(path: string): string[] { + if (statSync(path).isFile()) return [path]; + return readdirSync(path) + .sort() + .flatMap((name) => { + const child = resolve(path, name); + return statSync(child).isDirectory() || child.endsWith(".ts") ? sourceFiles(child) : []; + }); +} + +export function inspectMechanism(directory: string): { + hits: PolicyWordHit[]; + files: number; + errors: string[]; +} { + const files = MECHANISM_PATHS.flatMap((path) => sourceFiles(resolve(directory, path))); + const hits = files.flatMap((path) => + collectPolicyWords(relative(directory, path).replaceAll("\\", "/"), readFileSync(path, "utf8")), + ); + const errors = CLASSIFICATIONS.filter((site) => !POLICY_WORDS.includes(site.word)).map( + (site) => `${siteKey(site)}: word outside the maintained list`, + ); + errors.push(...comparePolicyWords(hits, CLASSIFICATIONS)); + return { hits, files: files.length, errors }; +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const report = inspectMechanism(process.cwd()); + console.log( + `policy words: ${report.hits.length} occurrences in ${report.files} files, ${report.errors.length} errors`, + ); + for (const error of report.errors) console.error(error); + if (process.argv.includes("--list")) { + for (const site of CLASSIFICATIONS) { + const lines = report.hits + .filter((hit) => siteKey(hit) === siteKey(site)) + .map((hit) => hit.line); + console.log( + `${siteKey(site)} ${site.classification} count=${site.count} lines=${lines.join(",")}: ${site.reason}`, + ); + } + } + if (report.errors.length) process.exitCode = 1; +} diff --git a/tools/policy-word-list.ts b/tools/policy-word-list.ts new file mode 100644 index 00000000..76505e30 --- /dev/null +++ b/tools/policy-word-list.ts @@ -0,0 +1,1095 @@ +export const POLICY_WORDS: readonly string[] = [ + "patch", + "editable", + "files", + "instruction", + "checks", + "repair-first", +]; + +/** The measured middle, plus the compiler model and the shared session mechanism. */ +export const MECHANISM_PATHS: readonly string[] = [ + "src/core/store", + "src/integration/ooo-board.ts", + "src/integration/ooo-dispatch.ts", + "src/integration/ooo-execution.ts", + "src/integration/ooo-candidate.ts", + "src/integration/ooo-fusion-plan.ts", + "src/integration/ooo-session-mechanism.ts", + "src/integration/task-semantics.ts", + "src/integration/task-semantics-model.ts", + "src/integration/task-semantics-interleavings.ts", +]; + +export interface ClassifiedSite { + path: string; + scope: string; + word: string; + count: number; + classification: "mechanism" | "policy" | "undecided"; + reason: string; +} + +// Counts are maintained by review, never refreshed automatically by the checker. +export const CLASSIFICATIONS: readonly ClassifiedSite[] = [ + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.freezeTaskRunTask", + word: "patch", + count: 2, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.freezeTaskRunTask", + word: "files", + count: 1, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.freezeTaskRunTask", + word: "editable", + count: 1, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.insertTaskRunTask", + word: "patch", + count: 8, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.insertTaskRunTask", + word: "files", + count: 4, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.insertTaskRunTask", + word: "editable", + count: 4, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.taskRunTasks", + word: "patch", + count: 8, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.taskRunTasks", + word: "files", + count: 4, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/base.ts", + scope: "NmgStoreBase.taskRunTasks", + word: "editable", + count: 4, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/maintenance.ts", + scope: "withMaintenance.recordActiveGraphAttribution", + word: "patch", + count: 1, + classification: "mechanism", + reason: "SQLite json_patch merges diagnostic timing fields; not a work shape.", + }, + { + path: "src/core/store/schema.ts", + scope: "migrate", + word: "patch", + count: 2, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/schema.ts", + scope: "migrate", + word: "editable", + count: 1, + classification: "policy", + reason: + "Persists or parses the patch-specific frozen declaration rather than an opaque work shape.", + }, + { + path: "src/core/store/schema.ts", + scope: "migrate", + word: "files", + count: 2, + classification: "undecided", + reason: + "One DDL literal contains both patch_files storage and filesystem fingerprint prose; ownership is mixed.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "", + word: "patch", + count: 8, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "PatchTaskSpec", + word: "patch", + count: 4, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "PatchTaskSpec", + word: "instruction", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "PatchTaskSpec", + word: "files", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "PatchTaskSpec", + word: "editable", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "Row", + word: "patch", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "Row", + word: "files", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "Row", + word: "editable", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardTicket", + word: "patch", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission", + word: "patch", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.constructor", + word: "patch", + count: 10, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.constructor", + word: "editable", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.constructor", + word: "files", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.constructor", + word: "instruction", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.createManifestTables", + word: "patch", + count: 4, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.createManifestTables", + word: "editable", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.createManifestTables", + word: "files", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.createCheckTable", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.migrateToTaskTables", + word: "checks", + count: 14, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.migrateToTaskTables", + word: "patch", + count: 4, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.migrateToTaskTables", + word: "editable", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.migrateToTaskTables", + word: "files", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.patchSpec", + word: "patch", + count: 3, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.patchFrozen", + word: "patch", + count: 4, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.patchFrozen", + word: "instruction", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.patchFrozen", + word: "files", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.patchFrozen", + word: "editable", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.inputDigest", + word: "patch", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.externalReady", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.issueCheck", + word: "checks", + count: 2, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.cancelCheck", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.checkRecord", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.submitCheck", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.projection", + word: "patch", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.claim", + word: "patch", + count: 4, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.proposalCommit", + word: "patch", + count: 6, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.proposalCommit", + word: "files", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.installPatchTask", + word: "patch", + count: 4, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.submit", + word: "patch", + count: 1, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.verifyCandidate", + word: "patch", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.cancel", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.refreshDerived", + word: "patch", + count: 2, + classification: "policy", + reason: "The board ticket, schema, freeze or submission path knows the patch work shape.", + }, + { + path: "src/integration/ooo-board.ts", + scope: "BoardAdmission.abandonCheck", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "", + word: "patch", + count: 5, + classification: "policy", + reason: "Dispatch tickets and workers require and reconstruct the frozen patch work shape.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "WorkerMetrics", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "PlanWorker", + word: "patch", + count: 1, + classification: "policy", + reason: "Dispatch tickets and workers require and reconstruct the frozen patch work shape.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "DispatchTicket", + word: "patch", + count: 2, + classification: "policy", + reason: "Dispatch tickets and workers require and reconstruct the frozen patch work shape.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "dispatchUnit", + word: "patch", + count: 4, + classification: "policy", + reason: "Dispatch tickets and workers require and reconstruct the frozen patch work shape.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "dispatchUnit", + word: "instruction", + count: 2, + classification: "policy", + reason: "Dispatch tickets and workers require and reconstruct the frozen patch work shape.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "dispatchUnit", + word: "files", + count: 2, + classification: "policy", + reason: "Dispatch tickets and workers require and reconstruct the frozen patch work shape.", + }, + { + path: "src/integration/ooo-dispatch.ts", + scope: "dispatchUnit", + word: "editable", + count: 2, + classification: "policy", + reason: "Dispatch tickets and workers require and reconstruct the frozen patch work shape.", + }, + { + path: "src/integration/ooo-candidate.ts", + scope: "DataCheck", + word: "files", + count: 1, + classification: "policy", + reason: "The data-check contract binds candidate values to a file-shaped mapping.", + }, + { + path: "src/integration/ooo-candidate.ts", + scope: "verifyCandidate", + word: "files", + count: 3, + classification: "mechanism", + reason: "Writes caller-supplied relative paths at the filesystem runner boundary.", + }, + { + path: "src/integration/ooo-candidate.ts", + scope: "verifyCandidate", + word: "checks", + count: 4, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-candidate.ts", + scope: "verifyDataChecks", + word: "checks", + count: 5, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-candidate.ts", + scope: "verifyDataChecks", + word: "files", + count: 3, + classification: "policy", + reason: "The data-check contract binds candidate values to a file-shaped mapping.", + }, + { + path: "src/integration/ooo-fusion-plan.ts", + scope: "", + word: "repair-first", + count: 1, + classification: "policy", + reason: "The protocol constraint vocabulary is still declared inside the planner module.", + }, + { + path: "src/integration/ooo-fusion-plan.ts", + scope: "nextSessionMove", + word: "repair-first", + count: 2, + classification: "mechanism", + reason: "Checks the plan-declared constraint and reports why this session cannot continue.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "", + word: "patch", + count: 6, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "CheckTool", + word: "files", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "checkToolCandidate", + word: "patch", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "checkToolCandidate", + word: "files", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "ArtifactParams", + word: "files", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "artifactEnvelope", + word: "patch", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "artifactEnvelope", + word: "files", + count: 4, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "patchEnvelope", + word: "patch", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "patchEnvelope", + word: "files", + count: 3, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "conclusionEnvelope", + word: "patch", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "conclusionEnvelope", + word: "files", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "PatchExecOptions", + word: "patch", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "patchSessionInput", + word: "patch", + count: 4, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "patchSessionInput", + word: "files", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "piCompletionAllowed", + word: "patch", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "PiRun", + word: "checks", + count: 1, + classification: "mechanism", + reason: + "Executes or records caller-declared checks; does not choose their wording or enablement.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "UnitState", + word: "patch", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "snapshotText", + word: "patch", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "snapshotText", + word: "files", + count: 4, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "snapshotText", + word: "instruction", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "snapshotText", + word: "editable", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "artifactFromText", + word: "patch", + count: 1, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "artifactFromText", + word: "files", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/ooo-session-mechanism.ts", + scope: "SessionRunInput", + word: "patch", + count: 2, + classification: "policy", + reason: + "Patch-specific worker inputs, artifact envelopes or prompt rendering remain in the session module.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "", + word: "patch", + count: 7, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "TaskUnit", + word: "instruction", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "TaskUnit", + word: "patch", + count: 3, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "TaskUnit", + word: "files", + count: 2, + classification: "undecided", + reason: + "Combines patch envelope paths with input-file granularity; the latter remains an experiment question.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "TaskUnit", + word: "editable", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "CompileInput", + word: "patch", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "", + word: "instruction", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "", + word: "files", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "", + word: "editable", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "within", + word: "patch", + count: 2, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refuseForeignSpecs", + word: "patch", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refuseOutOfRange", + word: "patch", + count: 3, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refusePermissionExpansion", + word: "patch", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refusePermissionExpansion", + word: "editable", + count: 2, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refusePermissionExpansion", + word: "files", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refuseSpecFields", + word: "patch", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "freezeEnvelope", + word: "patch", + count: 3, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "freezeEnvelope", + word: "instruction", + count: 2, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "freezeEnvelope", + word: "files", + count: 4, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "freezeEnvelope", + word: "editable", + count: 4, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "frozenPatch", + word: "patch", + count: 3, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "frozenPatch", + word: "files", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "unitFor", + word: "patch", + count: 7, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "unitFor", + word: "instruction", + count: 2, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "unitFor", + word: "files", + count: 2, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "unitFor", + word: "editable", + count: 1, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refuseWidening", + word: "patch", + count: 3, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, + { + path: "src/integration/task-semantics.ts", + scope: "refuseWidening", + word: "editable", + count: 3, + classification: "policy", + reason: + "The compiler or refinement rule names patch-specific fields, validation and permissions.", + }, +];