Skip to content

feat(F257): #3 objective registry — read-only discovery (single authority + fail-closed) - #48

Merged
mindfn merged 4 commits into
develop_basefrom
feat/f257-objective-registry
Jul 21, 2026
Merged

feat(F257): #3 objective registry — read-only discovery (single authority + fail-closed)#48
mindfn merged 4 commits into
develop_basefrom
feat/f257-objective-registry

Conversation

@mindfn

@mindfn mindfn commented Jul 21, 2026

Copy link
Copy Markdown
Owner

What

F257 修复清单 #3 — objective registry (只读发现). Replaces the archaeology loop where report_harness_signal's free-string objectiveId forced cats to hunt for valid objectives ("三次上报三次考古"): a canonical id + statement YAML registry (docs/harness-feedback/objectives/) + loader + GET /api/callbacks/objectives + cat_cafe_list_objectives MCP tool (A_READ_LOCAL, readonly allowlist) + report_harness_signal description sync.

Why

修复清单 #3 (V1 上线首日实测): objectiveId 为自由 string,无 list 工具 / schema 枚举 / harness 注入清单 → 三次上报三次考古 (含一次归因困难降权 0.6). 只读发现接口终结考古。

Design (single authority + fail-closed)

  • Definition layer only: id + statement. Unit→objective 挂靠 membership stays in the versioned UnitEvaluationManifest (redesign §4.8) — this registry is NOT a second writable authority.
  • Fail-closed loader (ObjectiveRegistryResult discriminated union): unreadable / malformed YAML / non-mapping / unsupported version (exactly v1) / non-array objectives / any invalid row / duplicate id / unknown key (incl. segments) → explicit failure, never a silent empty catalog.
  • Route: 503 (uncached) on failure with a path-free body (server logs the internal reason) — unauthenticated endpoint must not leak install path / fs errno.
  • discovery-only (objectiveId stays free-string; soft fail-closed validation deferred to V2).

Cross-family review (缅因猫 sol)

R1 (4 findings: single authority / fail-closed / MCP contract tests / architecture map) → R2 (path-free 503 / strict key-set) → R3 (pin version to exactly 1) → R4 APPROVE @ 0a357cfea (0 P1/P2/P3).

Evidence

  • Full api gate (pnpm --dir packages/api test): 19148 pass / 69 fail — all 69 pre-existing develop_base baseline (capabilities routes / audit-cc / F188 fixtures / eval:harness-ledger registration / CLAUDE.md content checks / Redis-timeout suites), categorically unrelated to objective registry; the F257 #3 suites all pass in the gate. Isolated change → 0 new failures.
  • Targeted: f257-objective-registry 23/0, callback-docs-route 4/0, full mcp-server suite 391/391 (list_objectives + report_harness_signal contract).
  • Biome clean (full-repo BIOME GUARD + BRAND GUARD pass); git diff --check clean.

🤖 Generated with Claude Code

mindfn and others added 4 commits July 21, 2026 14:23
…ectiveId 考古)

修复清单 #3:report_harness_signal 的 objectiveId 是自由 string,零 list/schema/注入清单
→ 三次上报三次考古。本 PR 建 objective registry 只读发现层:

- registry YAML(docs/harness-feedback/objectives/registry.yaml,KD-3 registry-as-data)=
  定义层(id/statement/segments)。**只登 canonized 目标**——redesign §2 名义 8 objectives
  但只 canonize 了 2 个 id(obj-routing-delivery + obj-identity-integrity);不 invent 另
  7 个 id(会伪造 V2 依赖的契约),registry 随 canonize 增长。
- loader(objective-registry.ts):parseObjectiveRegistry 纯函数(malformed 行丢弃不抛、
  malformed YAML → 空)+ loadObjectiveRegistry fail-safe。
- GET /api/callbacks/objectives(镜像 rich-block-rules 只读路由)。
- cat_cafe_list_objectives MCP tool(镜像 rich-block-rules-tool;A_READ_LOCAL + readonly
  allowlist 注册)。
- report_harness_signal objectiveId description 同步指向 list_objectives。

边界:只做定义层 + 发现,不做 V2(evaluator/metrics/condition registry)。objectiveId 仍
自由(discovery-only,软校验留 V2)。

证据:9 tests(parseObjectiveRegistry 4 + loadObjectiveRegistry 2 + 路由 e2e + shipped
registry 验证)全绿;api + mcp-server build clean;biome clean。切片 2a,独立 #47。

[宪宪/claude-opus-4-8🐾]

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ts + map delta

Addresses sol's slice-2a R1 REQUEST CHANGES (all four, no pushback).

## P1-1 — remove `segments` (second writable authority)
The registry authored `id/statement/segments`, duplicating the unit→objective
membership that frozen redesign §4.8 keeps in the versioned UnitEvaluationManifest.
Dropped `segments` from YAML + loader type + API output + MCP tool output; the
registry is now the canonical `id + statement` definition layer only. Attachments
become a V2 derived read-model from UnitEvaluationManifest — one authority.

## P1-2 — fail-closed loader (no silent empty), strict validation, lower complexity
parseObjectiveRegistry/loadObjectiveRegistry now return a discriminated
ObjectiveRegistryResult ({ok:true,registry} | {ok:false,error}). Unreadable file /
malformed YAML / non-mapping root / non-positive-integer version / non-array
objectives / any invalid row / duplicate id → explicit failure, never
{version:1,objectives:[]}. Validates positive-integer version, trimmed non-empty id
matching ^obj-[a-z0-9]+(?:-[a-z0-9]+)*$, trimmed non-empty statement, unique ids
(sol repros now rejected: version -2.5, whitespace id/statement, dup ids). Route
returns 503 (uncached) on failure; MCP tool surfaces it via its existing !ok error
path. Validation extracted into helpers → below the Biome complexity-17 warning.
Route gains an optional injectable registry path (test seam).

## P2-1 — MCP contract tests restored to true green
Added cat_cafe_list_objectives to EXPECTED_TOOLS / EXPECTED_COLLAB_TOOLS /
EXPECTED_READONLY_TOOLS, and cat_cafe_report_harness_signal to TOOLS/COLLAB (the
PR #42 leftover that made the suite baseline-red 385/387) — not masking the new
mismatch behind the old failure name. Added focused handler tests (success /
honest empty / 503 HTTP failure / network error). Full mcp-server suite 391/391.

## P2-2 — architecture map delta
harness-eval cell: added objective-registry.ts / callback-docs-routes.ts /
list-objectives-tool.ts code anchors + registry.yaml doc anchor + scan hints + why
this is the definition layer of the SAME control plane, not a parallel registry.

## Evidence
- api: f257-objective-registry 18/0, callback-docs-route 4/0 (200 + 503 fail-closed).
- mcp-server full suite 391/391 (was 385/387).
- biome: changed files clean (useLiteralKeys + parser complexity + format resolved).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n, not strip)

Addresses sol's 2a R2 REQUEST CHANGES (both small, no scope change). R1's four
findings were confirmed CLOSED.

## P2-1 — unauthenticated 503 leaked internal path / fs errno
callback-docs-routes.ts /api/callbacks/objectives is unauthenticated but returned the
loader's raw error, which contains the registry path + Node fs errno (e.g.
"...registry unreadable at /private/secret/install/objectives.yaml: ENOENT..."), forwarded
onward via the MCP tool's response.text(). Now: log the full internal reason server-side
(request.log.error) and return a stable, path-free 503 body `{ error: 'Objective registry
unavailable' }`. Test asserts the body contains neither the passed path nor ENOENT/errno,
and is uncached; the MCP tool still recognizes 503 as an errorResult.

## P2-2 — "strict + single authority" schema was still fail-open on forbidden fields
The loader read only id/statement and silently dropped everything else — so a stray
`segments` (or a typo / future unversioned field) could reappear in the YAML, mislead a
human into treating it as membership authority, while the runtime ignored it. Worse, the R1
test asserted that strip-behavior as "valid". Now registryVersion=1 fail-closes on the key
set: root allows only registryVersion/objectives; each entry allows only id/statement. A
`segments` key REJECTS (message points to the UnitEvaluationManifest §4.8 authority), not
strips; any unknown key rejects. Future fields must bump the schema. Removed the fail-open
"ignores segments" test; added red→green rejections for segments, unknown entry key, and
unknown root key.

## Evidence
- api: f257-objective-registry 21/0 (now includes segments/unknown-key rejections +
  descriptive-message assertion), callback-docs-route 4/0 (path-free 503 asserted).
- shipped registry.yaml still validates (id/statement only). biome changed files clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… on unsupported)

Addresses sol's 2a R3 sole finding (R2's two remain CLOSED).

## P2-1 — unsupported schema versions were accepted under v1 semantics
The key-set contract advertised "registryVersion=1 allows only … / future fields must bump
schema", but parseObjectiveRegistry accepted EVERY positive integer and applied the v1
parser (repro: v1/v2/v999 all ok:true). That advertises an unimplemented version back to
discovery clients as supported. Now the version gate requires exactly SUPPORTED_REGISTRY_
VERSION (=1); anything else fails closed with a clear message. A future v2 must ship an
explicit versioned parser + bump the constant, not ride the positive-integer gate.

Tests: changed the version-2 "valid" fixture to 1; added v2 + v999 rejection cases
(the existing -2.5 / 0 / 1.5 / missing cases still reject as ≠1).

## Evidence
- f257-objective-registry 23/0. biome changed files clean; build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mindfn
mindfn merged commit cb794a1 into develop_base Jul 21, 2026
@mindfn
mindfn deleted the feat/f257-objective-registry branch July 21, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant