feat(F257): #3 objective registry — read-only discovery (single authority + fail-closed) - #48
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
F257 修复清单 #3 — objective registry (只读发现). Replaces the archaeology loop where
report_harness_signal's free-stringobjectiveIdforced cats to hunt for valid objectives ("三次上报三次考古"): a canonicalid + statementYAML registry (docs/harness-feedback/objectives/) + loader +GET /api/callbacks/objectives+cat_cafe_list_objectivesMCP tool (A_READ_LOCAL, readonly allowlist) +report_harness_signaldescription sync.Why
修复清单 #3 (V1 上线首日实测):
objectiveId为自由 string,无 list 工具 / schema 枚举 / harness 注入清单 → 三次上报三次考古 (含一次归因困难降权 0.6). 只读发现接口终结考古。Design (single authority + fail-closed)
id + statement. Unit→objective 挂靠 membership stays in the versionedUnitEvaluationManifest(redesign §4.8) — this registry is NOT a second writable authority.ObjectiveRegistryResultdiscriminated 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.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
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; theF257 #3suites all pass in the gate. Isolated change → 0 new failures.f257-objective-registry23/0,callback-docs-route4/0, full mcp-server suite 391/391 (list_objectives + report_harness_signal contract).git diff --checkclean.🤖 Generated with Claude Code