Repository navigation
feat(cli): port the deterministic scenario scorer — shape score, artifact graph reader and Run grader — with a discrimination self-test - #30332
Conversation
Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
…30290) Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
…enario-scorer-port
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8749c7f16c2c0162a18014e8019ed8497ac74b00 && git checkout 8749c7f16c2c0162a18014e8019ed8497ac74b00
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bfca0efb79ac5df2049f392cdecd3bbeeea9ec61 2c7d2600eb3e847383cf9e567fbdf89d81e23241 && git checkout -B drift-repro bfca0efb79ac5df2049f392cdecd3bbeeea9ec61 && git merge --no-ff 2c7d2600eb3e847383cf9e567fbdf89d81e23241
node scripts/docs-audit/affected-docs.mjs --json bfca0efb79ac5df2049f392cdecd3bbeeea9ec61
|
|
epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu 席位核对Verdict: ACCEPT at head
|
…enario-scorer-port
…ond-object read census (#30290) Claude-Session: https://claude.ai/code/session_01Q4EjjPXwX1YfHyKJUh4agu Co-authored-by: Claude <noreply@anthropic.com>
|
epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu 席位核对 (patch round 1)Verdict: ACCEPT at head
|
Fixes #30290
Clause-②: no
Metadata-protocol: none
维护者速读
packages/cli/src/lint(三个新文件:结构打分scenario-score.ts、构建产物转打分图scenario-graph.ts、对话回合判分scenario-run-grade.ts),各带测试;不调用任何模型,不加命令行参数,不加包入口导出,目前没有调用方。dist/lint/scenario-*三个模块(包入口不可达);回滚即删这三个文件和它们的测试。packages/cli/src/lint/scenario-score.ts(看顶部「与云端源码的差异」一段和scoreGraph的导航检查、measureChecks);packages/cli/src/lint/scenario-graph.ts(只按 spec 的唯一形状读产物,读不懂就抛错);packages/cli/src/lint/scenario-score.test.ts(区分度自测)。pnpm --filter @objectstack/cli exec vitest run src/lint/scenario-score.test.ts src/lint/scenario-graph.test.ts src/lint/scenario-run-grade.test.ts,预期 3 个文件、33 个用例全绿。What changes
Ported under the cloud#3600 D2 ruling from the cloud eval harness (
structural-score.ts, the graph reader inagent-e2e.ts, andask-accuracy/scorer.ts), each file with the Apache-2.0 header and one provenance line. No model call anywhere; no new CLI flag; no new package-entry export.packages/cli/src/lint/scenario-score.ts:ScenarioExpectation(field-for-field the card'sexpectblock, mirrored from the sibling contract and not imported),EvalGraph,scoreGraph(exact names),ShapeExpectation,deriveShapeandscoreGraphShape(name-agnostic). The reference type set is the spec'sREFERENCE_VALUE_TYPES.packages/cli/src/lint/scenario-graph.ts:artifactToEvalGraph(artifact)reads a builtdist/objectstack.jsoninto the graph. Collections come throughresolveStackCollection(@objectstack/core, already re-exported byutils/stack-collections.ts), the rule the CLI and@objectstack/verifyshare, so a multi-package artifact with its collections only underpackages[](ADR-0130 option B) reads the same as a single-package one. ⛔ No local walk ofpackages[]. Each key is read in its one spec shape (fieldsrecord,referenceviareferenceTargetOf,navigation+ nestedchildren, widgetdataset+values); the cloud reader's alias chains (navigation ?? nav ?? tabs ?? …,reference_to ?? referenceTo ?? reference) are deliberately not carried over. An object with no name or afieldsthat is not a record throws aTypeErrorinstead of being read as an empty (degraded) object. A fixed-target type (user) gets noreferencein the graph; which types fix their target is asked of the spec (referenceTargetOf({ type })), not listed here.packages/cli/src/lint/scenario-run-grade.ts:gradeRun(truth, hidden, { replyText, toolCalls: [{ name, input, output }] })returns{ correct, cited, leaks, problems }. The dataset and the hosted-chat transcript type are cut out: the caller passes the truth figures and the hidden scope (objects, fields, hidden values, hidden aggregates). A tool call answered over a hidden field is detected structurally (every key and string in the input), not through a list of tool parameter names, so a rawsort/group_byspelling is still seen. Records-in-order and refusal-wording grading belonged to the cloud dataset's question kinds and are not ported.Two deviations from the cloud source, both measured
The discrimination self-test degrades each golden build one defect at a time and requires a strictly lower score. With the cloud semantics restored, two degradations score exactly as high as the intact build:
nav:TARGET->existsasked whether an object/dashboard/app of that name exists, not whether any app navigates to it. Now it passes only when an app nav entry targets the name and resolves, and every app nav entry is scored for resolving (nav-resolves:TARGET), as the shape scorer already did.!ds ||). A local artifact is the whole build, so that binding dangles and now fails, in both scorers.Smaller: the field target key is
reference(the spec's) rather thanreference_to, and the platform-object prefix issys_/ai_(nocloud_).Tests
All readings at
c0a6cf98(git rev-parse --short HEAD), the final commit, underscripts/pm/os-verify-lock.sh(shared-box seconds).pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 src/lint/scenario-score.test.ts src/lint/scenario-graph.test.ts src/lint/scenario-run-grade.test.tsgaveTest Files 3 passed (3)·Tests 33 passed (33).pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2gaveTest Files 387 passed (387)·Tests 5460 passed (5460). Theintegrationtier is left to CI: the diff touches no integration file, spawn entry, or driver/kernel boot path.pnpm --filter @objectstack/cli typecheckexited 0 (tsc --noEmit, thencheck:test-typecheck: OK).eslint --no-inline-config packages/cli/src/lint/scenario-*.tsexited 0.Discrimination self-test and the two ablations (one-time proof, not kept as tests)
Every intact golden build scores 1 on both scorers. Each variant (missing lookup, optionless select, dangling nav target, dangling dashboard measure, dangling dashboard dataset) scores strictly lower on both
scoreGraphandscoreGraphShape, over 5 builds (optionless select over 4).The cloud semantics were put back with
node scripts/ablation-replace.mjs(the anchor must hit, and it restores toblob == HEADwith an emptygit diff HEAD):targets.has(norm(navName))with the per-entrynav-resolvesloop removed. Result: 3 failed / 13 passed inscenario-score.test.ts. That includesdangling nav targetwithinvoice_with_line_items exact: expected 1 to be less than 1, which means the degraded build scored the same 1.00 as the intact one. Restored: blobeed52bc0d3ca== HEAD.!ds ||. Result: 1 failed / 15 passed,dangling dashboard datasetwithinvoice_with_line_items exact: expected 1 to be less than 1. Restored: blobeed52bc0d3ca== HEAD.Clause-② measurement
After
pnpm --filter @objectstack/cli build, I walked the declaration closure of the three package entries (dist/index.d.ts,dist/console.d.ts,dist/hook-body.d.ts). It holds 39 declaration files, and 0 of them are underdist/lint/. Positive control: the same walker started atdist/lint/scenario-graph.d.tsfinds 2 files underdist/lint/. Reading:Clause-②: no.Acceptance notes
resolveStackCollection(re-exported bypackages/cli/src/utils/stack-collections.ts), and it is reused.packages/verify/src/artifact-collections.ts'sdeclaredCollectionwas not used: it answers a different question (a truthy top level wins, so a declared-empty array behaves differently) and the CLI does not reach into@objectstack/verifyfor a read it already owns.corpus.tscarry objects only (no app, dataset or dashboard), so the self-test completes each with an app (nav over every object and the dashboard), one dataset and one dashboard, then parses the result withObjectStackDefinitionSchema(the parseos buildruns before writing the artifact). Every intact build scores 1 on both scorers; the reproduction of the "always 1.00" blind spot is the two ablations below, not the intact scores.blog_post_with_commentsdeclares no select, so the optionless-select variant runs on 4 of 5 builds; the test pins that count.carrier: cloud, reported to the seat, not filed here).Verification
Gate list from
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths: the script takes the change set from the merge-base). Each command ran atc0a6cf98, with its exit code captured before any pipe.--ranreconciliation:59 derived famil(ies) accounted for — 57 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 3 because its pinned positive-control commit was missing on a shallow checkout. Aftergit fetch --depth=1 origin 621a4876it exited 0.check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (it readsdist/of 10 packages outside this diff's build closure).check:i18n-coverage, reason: PREREQUISITE NOT MET (the showcase config loads packages without build output, e.g.@objectstack/service-print).Generated by Claude Code